6.6 — 4. Task Parallel Library
Mục tiêu bài học
- Nắm được ý chính của bài và mối liên hệ với module.
- Áp dụng được kiến thức vào bối cảnh CRM/.NET backend.
- Sẵn sàng chuyển sang bài kế tiếp với nền tảng chắc chắn.
Nội dung bài học
6.6.1 — Task.WhenAll — chạy song song
Task.WhenAll chờ tất cả tasks hoàn thành. Dùng khi các tasks độc lập nhau.
// Gửi thông báo qua 3 kênh song song (Email + SMS + Push)
public class NotificationService
{
private readonly IEmailSender _email;
private readonly ISmsSender _sms;
private readonly IPushSender _push;
public async Task SendAllChannelsAsync(
NotificationRequest request,
CancellationToken ct = default)
{
// Tạo 3 tasks — chúng chạy song song, không await từng cái
var emailTask = _email.SendAsync(request.Email, request.Subject, request.Body, ct);
var smsTask = _sms.SendAsync(request.Phone, request.ShortMessage, ct);
var pushTask = _push.SendAsync(request.DeviceToken, request.PushTitle, ct);
// Chờ cả 3 hoàn thành — tổng thời gian = task chậm nhất
await Task.WhenAll(emailTask, smsTask, pushTask);
}
// Thu thập kết quả từ nhiều tasks
public async Task<(CustomerDto customer, List<Order> orders, CreditScore credit)>
GetCustomerDashboardAsync(int customerId, CancellationToken ct = default)
{
var customerTask = _customerService.GetDtoAsync(customerId, ct);
var ordersTask = _orderService.GetByCustomerAsync(customerId, ct);
var creditTask = _creditService.GetScoreAsync(customerId, ct);
await Task.WhenAll(customerTask, ordersTask, creditTask);
return (customerTask.Result, ordersTask.Result, creditTask.Result);
}
}
6.6.2 — Task.WhenAny — lấy kết quả sớm nhất
// Timeout pattern với Task.WhenAny
public async Task<CustomerData?> GetWithTimeoutAsync(int id, CancellationToken ct)
{
var fetchTask = _api.FetchCustomerAsync(id, ct);
var timeoutTask = Task.Delay(TimeSpan.FromSeconds(3), ct);
var completed = await Task.WhenAny(fetchTask, timeoutTask);
if (completed == timeoutTask)
{
return null; // Timeout — trả về null thay vì throw
}
return await fetchTask; // fetchTask đã hoàn thành, lấy kết quả
}
6.6.3 — Parallel.ForEach so với async loop
// SAII — Parallel.ForEach là CPU-bound, không dùng cho async I/O
// Parallel.ForEach(customers, async c => await ProcessAsync(c)); // SAI!
// ĐÚNG cho I/O-bound: async loop hoặc WhenAll
public async Task ProcessAllCustomersAsync(List<Customer> customers, CancellationToken ct)
{
// Option 1: tuần tự (đơn giản, kiểm soát tốt)
foreach (var customer in customers)
{
await ProcessCustomerAsync(customer, ct);
}
// Option 2: song song tất cả (nhanh nhưng có thể quá tải DB)
var tasks = customers.Select(c => ProcessCustomerAsync(c, ct));
await Task.WhenAll(tasks);
// Option 3: song song với giới hạn concurrency
var semaphore = new SemaphoreSlim(10); // tối đa 10 concurrent
var tasks2 = customers.Select(async c =>
{
await semaphore.WaitAsync(ct);
try { await ProcessCustomerAsync(c, ct); }
finally { semaphore.Release(); }
});
await Task.WhenAll(tasks2);
}
Bài tập áp dụng
- Tóm tắt bài học bằng ngôn ngữ của bạn.
- Liên hệ nội dung với một tình huống thực tế trong dự án.
- Đề xuất một cải tiến cụ thể sau khi học bài này.
Tự kiểm tra
- Bạn có thể giải thích lại nội dung chính trong 2 phút không?
- Bạn có ví dụ áp dụng thực tế chưa?
- Bạn biết bước tiếp theo cần học/triển khai là gì không?
Kết luận
Hoàn thành bài này giúp bạn có góc nhìn đầy đủ hơn trước khi đi tiếp trong module.
Điều hướng
- Bài trước: 6.5 — 3. CancellationToken
- Bài tiếp theo: 6.7 — 5. Async Patterns
- Về module: Trang mục lục