Async/Await & Collections Nâng cao
Async/Await
Cơ chế hoạt động
async/await là cú pháp cho phép viết code bất đồng bộ một cách đồng bộ (synchronous-looking).
public async Task<string> FetchDataAsync()
{
using var client = new HttpClient();
var result = await client.GetStringAsync("https://api.example.com/data");
return result;
}
Luồng xử lý
- Khi gọi method
async, thread hiện tại không bị block - Task được tạo và chạy trên Thread Pool
- Khi async operation hoàn thành, continuation được schedule lại
- Kết quả được trả về cho caller
Lưu ý quan trọng
asyncmethod luôn trả vềTask,Task<T>, hoặcvoid(chỉ dùng cho event handlers)- Không nên dùng
.Resulthoặc.Wait()vì sẽ gây deadlock - Sử dụng
ConfigureAwait(false)để tránh context capture
IEnumerable vs IAsyncEnumerable
IEnumerable
public IEnumerable<Product> GetProducts()
{
return _context.Products; // Deferred execution
}
- Synchronous - load all data vào memory
- Deferred Execution - không thực thi cho đến khi được enumerate
- Phù hợp cho tập dữ liệu nhỏ
IAsyncEnumerable (.NET Core 2.1+)
public async IAsyncEnumerable<Product> GetProductsAsync()
{
await foreach (var product in _context.Products.AsAsyncEnumerable())
{
yield return product;
}
}
- Asynchronous - stream data từ database
- Non-blocking - không block thread
- Phù hợp cho tập dữ liệu lớn hoặc real-time streaming
So sánh
| Feature | IEnumerable | IAsyncEnumerable |
|---|---|---|
| Execution | Synchronous | Asynchronous |
| Memory | Load all | Stream |
| Performance | Chậm với large data | Tốt với large data |
| Use case | Small datasets | Large datasets, real-time |
LINQ
Deferred Execution
var query = products.Where(p => p.Price > 100); // Chưa execute
var result = query.ToList(); // Execute tại đây
- Query chỉ được thực thi khi:
- Gọi
.ToList(),.ToArray(),.Count(),.First(), etc. - Sử dụng
foreach
- Gọi
IEnumerable vs IQueryable
// IEnumerable - Thực thi trong memory
IEnumerable<Product> products = _context.Products.ToList();
var filtered = products.Where(p => p.Price > 100); // Filter in memory
// IQueryable - Thực thi trên database
IQueryable<Product> query = _context.Products;
var filteredQuery = query.Where(p => p.Price > 100); // Filter in SQL
| Feature | IEnumerable | IQueryable |
|---|---|---|
| Execution | In-memory | Database/Provider |
| Deferred Execution | ✅ Yes | ✅ Yes |
| Query Composition | LINQ to Objects | LINQ to Entities/SQL |
| Performance | Load all data first | Filter at database |
| Use case | Small datasets, in-memory | Database queries, large datasets |
Khi nào dùng IQueryable?
// ✅ Tốt - Filter ở database
var expensiveProducts = _context.Products
.Where(p => p.Price > 100)
.ToList(); // SQL: SELECT * FROM Products WHERE Price > 100
// ❌ Không tốt - Load all rồi filter
var allProducts = _context.Products.ToList(); // SELECT * FROM Products
var expensive = allProducts.Where(p => p.Price > 100); // Filter in memory
Tối ưu LINQ trên tập dữ liệu lớn
-
Sử dụng IQueryable thay vì IEnumerable cho database queries
public IQueryable<Product> GetProducts() => _context.Products; -
Avoid multiple enumerations
// Bad - Multiple enumerations (deferred execution) var query = products.Where(x => x.Active); // IEnumerable, chưa execute var count = query.Count(); // First enumeration - executes query var list = query.ToList(); // Second enumeration - executes query AGAIN! // Good - Materialize once var list = products.Where(x => x.Active).ToList(); // Materialize once var count = list.Count; // Use cached list, no re-enumeration -
Use pagination
Offset-based pagination (phù hợp cho admin dashboard, báo cáo):
// ✅ Offset-based - đơn giản, dễ implement var page = products .OrderBy(p => p.Id) .Skip((pageNumber - 1) * pageSize) .Take(pageSize) .ToList();Cursor-based pagination (phù hợp cho infinite scroll, mobile app, real-time feed):
// ✅ Cursor-based - hiệu suất cao, không bị "drift" khi data thay đổi // Client gửi cursor = Id của item cuối cùng đã load var page = products .Where(p => p.Id < lastSeenId) // hoặc > nếu sort ascending .OrderByDescending(p => p.Id) .Take(pageSize) .ToList(); // Trả về cursor cho client (Id của item cuối cùng) var nextCursor = page.LastOrDefault()?.Id;Loại Khi nào dùng Ưu điểm Nhược điểm Offset-based Admin dashboard, báo cáo, cần nhảy đến page cụ thể Đơn giản, dễ implement, hỗ trợ “jump to page N” Chậm với large offset, bị “drift” khi data thay đổi Cursor-based Infinite scroll, mobile app, real-time feed (Facebook, Twitter) Hiệu suất cao (dùng index), không bị drift Không nhảy page được, chỉ “next/prev” -
Select only needed columns
var names = products.Select(p => p.Name).ToList(); // Not full entity -
Eager loading vs Lazy loading
// Lazy loading (nhiều queries) var products = context.Products.ToList(); foreach (var p in products) { var category = p.Category; // Additional query mỗi lần } // Eager loading (1 query với join) var products = context.Products .Include(p => p.Category) .ToList();