Hãy tưởng tượng một kịch bản: Người dùng gửi yêu cầu đến API của bạn, nó bắt đầu xử lý, sau đó người dùng mất kiên nhẫn và đóng trình duyệt của họ. Còn API của bạn? Vẫn đang miệt mài làm việc mà không ai bao giờ nhìn thấy. Các truy vấn cơ sở dữ liệu đang chạy, các API bên ngoài đang được gọi, tất cả đều vô ích.
Đó là lúc CancellationToken xuất hiện. Đây là cách để bạn nói rằng ""này, nếu khách hàng không còn quan tâm nữa, hãy ngừng lãng phí tài nguyên đi.""
CancellationToken thực sự là gì?
Hãy coi CancellationToken như một người đưa tin báo cho mã của bạn biết ""khách hàng đã ngắt kết nối"" hoặc ""thao tác này đang mất quá nhiều thời gian, hãy bỏ cuộc đi."" Khi điều đó xảy ra, bạn có thể thoát sớm thay vì hoàn thành công việc mà không ai cần đến.
💡 Phần kỳ diệu
ASP.NET Core tự động tạo và truyền một CancellationToken vào các hành động trong controller của bạn. Khi yêu cầu HTTP bị hủy (người dùng đóng trình duyệt, hết thời gian chờ, v.v.), token đó sẽ bị hủy. Bạn chỉ cần sử dụng nó!
Ví dụ đơn giản nhất
Đây là điều hầu hết mọi người làm (cách sai):
[HttpGet(""products"")]
public async Task<IActionResult> GetProducts()
{
// Điều này sẽ tiếp tục chạy ngay cả khi khách hàng ngắt kết nối
var products = await _context.Products.ToListAsync();
return Ok(products);
}Còn đây là cách tốt hơn:
[HttpGet(""products"")]
public async Task<IActionResult> GetProducts(CancellationToken cancellationToken)
{
// Nếu khách hàng ngắt kết nối, việc này sẽ tự động dừng lại
var products = await _context.Products.ToListAsync(cancellationToken);
return Ok(products);
}Chỉ vậy thôi! Chỉ cần thêm tham số và truyền nó đi. ASP.NET Core sẽ xử lý phần còn lại.
Ví dụ thực tế: Tạo báo cáo
Giả sử bạn đang xây dựng một endpoint tạo báo cáo. Mất 5 giây để tạo một báo cáo bán hàng. Người dùng nhấp vào ""Tạo báo cáo"", đợi 2 giây, mất kiên nhẫn và đóng tab trình duyệt.
❌ Không có CancellationToken (Vấn đề)
[HttpGet(""reports/sales"")]
public async Task<IActionResult> GenerateSalesReport()
{
// Mô phỏng việc lấy dữ liệu từ cơ sở dữ liệu (2 giây)
await Task.Delay(2000);
var salesData = await _context.Sales.ToListAsync();
// Mô phỏng việc xử lý và tạo báo cáo (3 giây)
await Task.Delay(3000);
var report = GenerateReport(salesData);
return Ok(report);
}🔥 Vấn đề
• Người dùng đóng trình duyệt sau 2 giây
• Máy chủ của bạn vẫn tiếp tục chạy thêm 3 giây nữa
• Các kết nối cơ sở dữ liệu vẫn mở một cách không cần thiết
• Tài nguyên máy chủ bị lãng phí cho công việc mà không ai nhìn thấy
• Nhân con số này với 100 người dùng thiếu kiên nhẫn = sự cạn kiệt tài nguyên nghiêm trọng!
✅ Với CancellationToken (Giải pháp)
[HttpGet(""reports/sales"")]
public async Task<IActionResult> GenerateSalesReport(CancellationToken cancellationToken)
{
try
{
// Mô phỏng việc lấy dữ liệu từ cơ sở dữ liệu (2 giây)
await Task.Delay(2000, cancellationToken);
var salesData = await _context.Sales.ToListAsync(cancellationToken);
// Mô phỏng việc xử lý và tạo báo cáo (3 giây)
await Task.Delay(3000, cancellationToken);
var report = GenerateReport(salesData);
return Ok(report);
}
catch (OperationCanceledException)
{
// Khách hàng đã ngắt kết nối - ghi nhật ký và tiếp tục
_logger.LogInformation(""Việc tạo báo cáo đã bị khách hàng hủy"");
return StatusCode(499, ""Khách hàng đã đóng yêu cầu"");
}
}✨ Điều gì đã thay đổi?
• Người dùng đóng trình duyệt sau 2 giây
• CancellationToken được kích hoạt ngay lập tức
• Task.Delay ném ra ngoại lệ OperationCanceledException
• Quá trình xử lý dừng lại ngay lập tức - không lãng phí 3 giây!
• Các kết nối cơ sở dữ liệu được giải phóng ngay lập tức
• Tài nguyên máy chủ được giải phóng cho những người dùng thực sự
💡 Tác động thực tế
Hãy tưởng tượng kịch bản này ở quy mô lớn:
Không có CancellationToken:
100 người dùng thiếu kiên nhẫn × 5 giây lãng phí = 500 giây thời gian máy chủ bị lãng phí
Với CancellationToken:
Các thao tác dừng lại ngay lập tức khi bị hủy = Lãng phí tài nguyên tối thiểu
Truyền nó qua lớp dịch vụ của bạn
Đừng chỉ sử dụng nó trong các controller! Hãy truyền nó qua toàn bộ chuỗi gọi của bạn:
// Controller
[HttpGet(""orders/{id}"")]
public async Task<IActionResult> GetOrder(
int id,
CancellationToken cancellationToken)
{
var order = await _orderService.GetOrderDetailsAsync(id, cancellationToken);
return Ok(order);
}
// Service
public class OrderService
{
public async Task<OrderDto> GetOrderDetailsAsync(
int orderId,
CancellationToken cancellationToken)
{
var order = await _context.Orders
.Include(o => o.Items)
.FirstOrDefaultAsync(o => o.Id == orderId, cancellationToken);
// Gọi một dịch vụ khác
var shipping = await _shippingService
.GetShippingInfoAsync(order.ShippingId, cancellationToken);
return MapToDto(order, shipping);
}
}
// External API call
public class ShippingService
{
private readonly HttpClient _httpClient;
public async Task<ShippingInfo> GetShippingInfoAsync(
int shippingId,
CancellationToken cancellationToken)
{
var response = await _httpClient.GetAsync(
$""https://api.shipping.com/info/{shippingId}"",
cancellationToken); // HttpClient cũng hỗ trợ nó!
return await response.Content
.ReadFromJsonAsync<ShippingInfo>(cancellationToken);
}
}💡 Bạn có thấy CancellationToken chảy qua mọi lớp không? Đó chính là mô hình. Đừng dừng lại ở controller!
Các phương pháp tốt nhất
1. Luôn thêm CancellationToken vào các phương thức bất đồng bộ
Hãy đặt nó làm tham số cuối cùng. Theo mặc định, nó là tùy chọn (sử dụng CancellationToken.None).
2. Truyền nó qua toàn bộ ngăn xếp gọi của bạn
Controller → Service → Repository → Database. Mọi lớp đều nên tôn trọng việc hủy bỏ.
3. Đừng nuốt chửng OperationCanceledException
Hãy để nó lan truyền lên trên hoặc xử lý nó một cách cụ thể. Đừng bắt nó như một Exception chung chung rồi bỏ qua.
4. Kiểm tra việc hủy bỏ trong các vòng lặp chạy dài
Nếu bạn đang xử lý hàng nghìn mục, hãy kiểm tra sau mỗi N lần lặp là tối thiểu.
5. Hãy nhớ rằng HttpClient đã hỗ trợ nó
Hầu hết các thư viện .NET đều làm được! Luôn tìm kiếm các quá tải chấp nhận CancellationToken.
Khi nào KHÔNG nên sử dụng CancellationToken
🚫 Đừng sử dụng nó cho:
Bắt
OperationCanceledExceptionđể xử lý việc hủy bỏ một cách nhẹ nhàngĐừng sử dụng nó cho các thao tác quan trọng bắt buộc phải hoàn thành
Hãy bắt đầu thêm nó ngay hôm nay, API của bạn sẽ hiệu quả hơn và máy chủ của bạn sẽ bớt căng thẳng hơn!