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 Task và 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

  1. Task là gì?
  2. Task<T> khác Task thế nào?
  3. async có tạo thread mới không?
  4. await có block thread không?
  5. Async khác multithreading thế nào?
  6. I/O-bound là gì?
  7. CPU-bound là gì?
  8. Task.Run dùng khi nào?
  9. Task.Delay khác Thread.Sleep thế nào?
  10. WhenAll làm gì?
  11. WhenAny làm gì?
  12. Vì sao tránh async void?
  13. Khi nào async void chấp nhận được?
  14. .Result / .Wait() nguy hiểm ở đâu?
  15. CancellationToken có kill thread không?
  16. Async giúp ASP.NET Core scale thế nào?
  17. Async có làm DB query nhanh hơn không?
  18. WhenAll có tạo một thread cho mỗi task không?
  19. Có nên Task.Run quanh EF Core sync query không?
  20. 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"