C# Day 10: Async/Await và Task
Ngày 10 tập trung vào asynchronous programming trong C#.
Mục tiêu:
- hiểu
TaskvàTask<T> - hiểu
async/await - phân biệt async với multithreading
- hiểu I/O-bound vs CPU-bound
- biết khi nào dùng
Task.Run - hiểu
Task.Delay,WhenAll,WhenAny - hiểu
async void - hiểu exception trong async
- hiểu
CancellationToken - tránh
.Result/.Wait() - hiểu async trong ASP.NET Core
1. Synchronous vs Asynchronous
Synchronous:
var user = GetUser();
DoSomething(user);
Flow:
GetUser
↓
chờ xong
↓
DoSomething
Asynchronous:
var user = await GetUserAsync();
DoSomething(user);
Flow:
start operation
↓
operation chưa xong
↓
method có thể yield control
↓
operation hoàn tất
↓
continue
Cần nhớ:
Async
≠ tự động chạy nhanh hơn
Async
≠ tự động tạo thread mới
2. Task là gì?
Task đại diện cho một operation sẽ hoàn thành trong tương lai.
Ví dụ:
Task SaveAsync()
nghĩa là operation có completion nhưng không trả business result.
Dùng:
await repository.SaveAsync();
Mental model:
Task
→ representation of asynchronous work
3. Task<T> là gì?
Ví dụ:
Task<User> GetUserAsync()
nghĩa là operation async sau này trả User.
User user = await GetUserAsync();
Mental model:
Task
→ completion
Task<T>
→ completion + result T
4. async
public async Task<User> GetUserAsync()
{
...
}
async cho phép method sử dụng await.
Nhưng:
async
≠ background thread
≠ parallelism
≠ performance magic
5. await
var user =
await repository.GetAsync(id);
Flow:
GetAsync()
↓
return Task<User>
↓
Task chưa complete
↓
await
↓
method tạm nhường control
↓
Task complete
↓
method tiếp tục
Mental model:
await
→ asynchronously wait
6. await có block thread không?
Với proper async I/O:
var users =
await dbContext.Users
.ToListAsync();
thread không cần ngồi block chờ DB.
Mental model:
request thread
↓
start DB operation
↓
await
↓
thread được trả về pool
↓
DB xử lý
↓
operation complete
↓
continuation được schedule
7. await có tạo thread mới không?
Không nhất thiết.
await httpClient.GetAsync(url);
không đồng nghĩa:
create Thread #2
Mental model:
async
→ non-blocking waiting
không phải
→ thread creation
8. Async vs Multithreading
Async:
giải quyết waiting
Multithreading:
nhiều thread có thể execute work
Ví dụ:
await dbContext.Users.ToListAsync();
là async I/O, không phải “mỗi query có một thread riêng”.
9. I/O-bound
Ví dụ:
Database
HTTP
File I/O
Network
Socket
Cloud Storage
Thường dùng native async API:
await dbContext.Users.ToListAsync();
await httpClient.GetAsync(url);
Mental model:
CPU không phải bottleneck chính
→ đang chờ I/O
10. CPU-bound
Ví dụ:
image processing
compression
encryption
heavy calculation
CPU đang thật sự bận xử lý.
Có thể cân nhắc:
var result = await Task.Run(
() => CalculateReport()
);
11. Task.Run
Mental model:
Task.Run
→ queue CPU work lên ThreadPool
Không nên dùng để fake async cho I/O.
Bad:
var users = await Task.Run(
() => dbContext.Users.ToList()
);
Better:
var users =
await dbContext.Users.ToListAsync();
12. Task.Delay
await Task.Delay(1000);
khác:
Thread.Sleep(1000);
Thread.Sleep:
block thread
Task.Delay:
non-blocking wait
13. Task.WhenAll
var userTask = GetUserAsync();
var orderTask = GetOrdersAsync();
await Task.WhenAll(
userTask,
orderTask
);
Mental model:
start User
start Orders
↓
wait tất cả
14. Sequential vs Concurrent Async
Sequential:
var user = await GetUserAsync();
var orders = await GetOrdersAsync();
Nếu mỗi call 500 ms:
≈ 1000 ms
Concurrent:
var userTask = GetUserAsync();
var orderTask = GetOrdersAsync();
await Task.WhenAll(
userTask,
orderTask
);
Nếu độc lập:
≈ max(500ms, 500ms)
≈ 500 ms
15. WhenAll không đồng nghĩa Parallel CPU
Task.WhenAll chỉ có nghĩa:
wait nhiều operations cùng hoàn tất
Không có nghĩa:
mỗi task = một thread
16. Task.WhenAny
var completedTask =
await Task.WhenAny(
taskA,
taskB
);
Mental model:
taskA
vs
taskB
↓
ai complete trước?
Useful cho:
timeout
fallback
race giữa nhiều sources
17. async void
Không nên:
async void DoWork()
Nên:
async Task DoWorkAsync()
Vì caller của async void không thể dễ dàng:
await
compose
observe completion
handle exception theo Task flow
Use case chính của async void:
event handler
18. Exception trong Async
try
{
await DoWorkAsync();
}
catch (Exception ex)
{
}
Nếu task faulted, exception được propagate tại await.
Task có thể ở các state:
Running
RanToCompletion
Faulted
Canceled
19. Không await Task
Bad:
DoWorkAsync();
Rủi ro:
không biết khi nào complete
exception khó observe đúng nơi
lifecycle khó kiểm soát
Rule:
Ai tạo Task
→ nên biết ai chịu trách nhiệm await/observe nó
20. Fire-and-Forget
Trong backend production, fire-and-forget cần rất có chủ đích.
Bad:
SendEmailAsync();
trong HTTP request rồi bỏ Task.
Rủi ro:
request kết thúc
scope dispose
exception bị bỏ
work chưa xong
Background work thường nên có:
queue
background worker
message broker
job scheduler
21. .Result
Bad:
var user =
GetUserAsync().Result;
Đây là sync-over-async.
Thread bị block.
22. .Wait()
Bad:
GetUserAsync().Wait();
Cũng block thread.
Possible problems:
thread blocking
deadlock trong một số context
ThreadPool starvation
reduced scalability
Mental model:
async all the way
23. CancellationToken
public async Task<User> GetUserAsync(
Guid id,
CancellationToken cancellationToken)
{
return await dbContext.Users
.FirstAsync(
x => x.Id == id,
cancellationToken
);
}
Mental model:
CancellationToken
→ signal yêu cầu cancel
24. Cancellation là Cooperative
CancellationToken không kill thread.
Operation phải cooperate:
cancellationToken
.ThrowIfCancellationRequested();
Mental model:
"Please stop when safe."
25. ASP.NET Core và CancellationToken
Controller:
[HttpGet("{id}")]
public async Task<IActionResult> Get(
Guid id,
CancellationToken cancellationToken)
{
var user =
await service.GetAsync(
id,
cancellationToken
);
return Ok(user);
}
Nên propagate:
Controller
↓
Service
↓
Repository
↓
EF Core / HTTP client
26. ConfigureAwait
await SomeOperationAsync()
.ConfigureAwait(false);
Concept:
continuation có cần quay lại captured context không?
Trong ASP.NET Core hiện đại, không nên mechanically thêm ConfigureAwait(false) ở mọi chỗ.
Day 10 chỉ cần hiểu concept.
27. Async State Machine
Compiler biến async method thành state machine.
Ví dụ:
public async Task<int> GetValueAsync()
{
var a = await GetAAsync();
var b = await GetBAsync();
return a + b;
}
Compiler cần nhớ:
đang ở bước nào
local variables
Task nào đang chờ
continuation tiếp tục ở đâu
Mental model:
async method
→ compiler-generated state machine
28. Await khi Task đã Complete
Nếu task đã complete:
await task;
có thể tiếp tục gần như ngay.
Mental model:
Task complete?
├── Yes → continue
└── No → register continuation + yield
29. ASP.NET Core Request Flow
[HttpGet]
public async Task<IActionResult> Get(
CancellationToken cancellationToken)
{
var users =
await dbContext.Users
.ToListAsync(
cancellationToken
);
return Ok(users);
}
Flow:
HTTP Request
↓
Controller
↓
DB query starts
↓
await
↓
thread trở về ThreadPool
↓
DB responds
↓
continuation
↓
HTTP Response
30. Vì sao Async giúp Web API Scale?
Nếu nhiều requests đều đang chờ DB:
Blocking:
nhiều threads
→ ngồi chờ I/O
Async:
threads không bị giữ chỉ để chờ
→ ThreadPool phục vụ work khác
Async cải thiện:
throughput
scalability
resource utilization
Không có nghĩa từng DB query chạy nhanh hơn.
31. Async không làm DB Query nhanh hơn
Nếu DB query mất 2 giây:
Sync:
thread block khoảng 2 giây
Async:
DB vẫn mất khoảng 2 giây
nhưng thread không bị giữ suốt thời gian chờ
32. Không phải Method nào cũng cần Async
Ví dụ:
public string NormalizeName(
string value)
{
return value
.Trim()
.ToLowerInvariant();
}
Đây là small synchronous CPU work.
Không cần biến thành async.
33. Task.FromResult
public Task<int> GetValueAsync()
{
return Task.FromResult(10);
}
Không sai, nhưng không biến CPU work thành async.
Useful trong một số:
abstraction
test
cache
already-computed result
34. Common Mistake — Await tuần tự khi Work độc lập
var profile =
await GetProfileAsync();
var settings =
await GetSettingsAsync();
Nếu độc lập, có thể:
var profileTask =
GetProfileAsync();
var settingsTask =
GetSettingsAsync();
await Task.WhenAll(
profileTask,
settingsTask
);
35. Không Parallelize mọi thứ
Ví dụ cùng dùng một EF Core DbContext:
var task1 =
dbContext.Users.ToListAsync();
var task2 =
dbContext.Orders.ToListAsync();
await Task.WhenAll(task1, task2);
Có thể không hợp lệ vì DbContext không hỗ trợ multiple concurrent operations trên cùng instance.
Rule:
Independent operations
+
dependencies support concurrency
→ mới cân nhắc WhenAll
36. Không Task.Run mọi thứ trong ASP.NET Core
Không nên:
await Task.Run(
() => SomeDatabaseCall()
);
chỉ để biến sync thành async.
ASP.NET Core đã sử dụng ThreadPool cho request processing.
Với I/O-bound work, ưu tiên native async API.
37. Propagate CancellationToken
Bad:
await service.GetAsync(id);
khi controller đã có token.
Better:
await service.GetAsync(
id,
cancellationToken
);
và tiếp tục truyền xuống repository.
38. Cancellation không phải Generic 500
Cancellation thường liên quan:
OperationCanceledException
TaskCanceledException
Request canceled không đồng nghĩa application bug.
Không nên blindly map mọi cancellation thành 500.
39. Production Example — Repository
public async Task<User?> GetByIdAsync(
Guid id,
CancellationToken cancellationToken)
{
return await dbContext.Users
.AsNoTracking()
.FirstOrDefaultAsync(
x => x.Id == id,
cancellationToken
);
}
40. Production Example — Service
public async Task<UserDto?> GetAsync(
Guid id,
CancellationToken cancellationToken)
{
var user =
await repository.GetByIdAsync(
id,
cancellationToken
);
if (user is null)
{
return null;
}
return new UserDto
{
Id = user.Id,
Name = user.Name
};
}
41. Production Example — Controller
[HttpGet("{id:guid}")]
public async Task<IActionResult> Get(
Guid id,
CancellationToken cancellationToken)
{
var user =
await service.GetAsync(
id,
cancellationToken
);
if (user is null)
{
return NotFound();
}
return Ok(user);
}
Flow:
Controller async
↓
Service async
↓
Repository async
↓
EF Core async
Đây là:
async all the way
42. Production Example — Independent HTTP Calls
var profileTask =
profileClient.GetAsync(
userId,
cancellationToken
);
var rewardTask =
rewardClient.GetAsync(
userId,
cancellationToken
);
await Task.WhenAll(
profileTask,
rewardTask
);
var profile =
await profileTask;
var rewards =
await rewardTask;
Phù hợp khi hai operations độc lập và dependencies support concurrency.
43. Exception + WhenAll
try
{
await Task.WhenAll(tasks);
}
catch (Exception ex)
{
}
Nếu một hoặc nhiều task fail:
combined task
→ Faulted
Day 10 chỉ cần nắm mental model này; exception aggregation sâu hơn có thể học bổ sung.
44. Mental Model Tổng Kết
Task
→ async operation
Task<T>
→ async operation + result
async
→ cho phép await
await
→ asynchronously wait
Task.Run
→ ThreadPool work
Task.Delay
→ non-blocking delay
WhenAll
→ wait all
WhenAny
→ wait first
CancellationToken
→ cooperative cancellation
.Result / .Wait()
→ blocking
45. CPU-bound vs I/O-bound
I/O-bound
→ DB / HTTP / File / Network
→ use native async API
CPU-bound
→ calculation / processing
→ CPU actually busy
→ Task.Run có thể phù hợp tùy context
46. Câu Hỏi Ôn Tập
Tasklà gì?Task<T>khácTaskthế nào?asynccó tạo thread mới không?awaitcó block thread không?- Async khác multithreading thế nào?
- I/O-bound là gì?
- CPU-bound là gì?
Task.Rundùng khi nào?Task.DelaykhácThread.Sleepthế nào?WhenAlllàm gì?WhenAnylàm gì?- Vì sao tránh
async void? - Khi nào
async voidchấp nhận được? .Result/.Wait()nguy hiểm ở đâu?CancellationTokencó kill thread không?- Async giúp ASP.NET Core scale thế nào?
- Async có làm DB query nhanh hơn không?
WhenAllcó tạo một thread cho mỗi task không?- Có nên
Task.Runquanh EF Core sync query không? - Vì sao phải propagate
CancellationToken?
47. Bài Tập Ngày 10
Bài 1 — Task và Task<T>
Viết:
Task SaveAsync()
và:
Task<User> GetUserAsync()
Giải thích khác nhau.
Bài 2 — Task.Delay vs Thread.Sleep
Viết hai version và giải thích thread behavior.
Bài 3 — Sequential vs WhenAll
Có hai operations mỗi cái khoảng 500ms.
Viết version sequential và concurrent, sau đó đo elapsed time.
Bài 4 — WhenAny
Tạo:
Task A = 1 second
Task B = 2 seconds
Xác định task nào hoàn thành trước.
Bài 5 — Exception
Tạo method async throw sau Task.Delay, sau đó catch ở caller bằng await.
Bài 6 — Cancellation
Tạo loop:
while (...)
{
cancellationToken
.ThrowIfCancellationRequested();
await Task.Delay(
500,
cancellationToken
);
}
Cancel từ caller.
Bài 7 — .Result
So sánh:
var result =
GetValueAsync().Result;
với:
var result =
await GetValueAsync();
Bài 8 — ASP.NET Core Flow
Viết:
Controller
→ Service
→ Repository
→ EF Core
toàn bộ sử dụng async/await và CancellationToken.
Bài 9 — Không Fake Async
Giải thích vì sao Task.Run không phải solution tốt cho DB I/O nếu provider đã có native async API.
Bài 10 — Khi nào không nên WhenAll?
Phân tích:
Task B phụ thuộc Task A
same DbContext
shared mutable state
rate-limited API
48. Production Exercise
Thiết kế endpoint:
GET /dashboard
cần lấy:
Profile
Orders
Notifications
Rewards
Yêu cầu:
1. Xác định operations độc lập
2. Dùng WhenAll ở nơi phù hợp
3. Propagate CancellationToken
4. Không Task.Run cho HTTP/DB calls
5. Handle exception
6. Không fire-and-forget
7. Kiểm tra shared dependency có support concurrency không
Sau đó trả lời:
Async giúp endpoint nhanh hơn ở đâu?
Async giúp server scale ở đâu?
WhenAll giảm latency như thế nào?
Nếu cùng DbContext thì sao?
Nếu một task fail thì sao?
Key Takeaways
async
≠ new thread
await
≠ block thread
Task
≠ thread
Mental model nên giữ:
Task
→ operation
await
→ chờ operation theo cách asynchronous
I/O-bound
→ native async API
CPU-bound
→ CPU work
WhenAll
→ concurrent independent operations
CancellationToken
→ cooperative cancellation
Luôn tự hỏi:
Operation này đang chờ I/O hay dùng CPU?
Có native async API không?
Tôi có đang block bằng .Result/.Wait() không?
Các tasks có thật sự independent không?
Dependency có support concurrency không?
CancellationToken đã được propagate chưa?
Ai chịu trách nhiệm observe Task này?
Nếu trả lời được các câu trên, bạn đã đi từ:
"biết dùng async/await"
sang:
"hiểu async execution model trong production .NET"