Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

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ý

  1. Khi gọi method async, thread hiện tại không bị block
  2. Task được tạo và chạy trên Thread Pool
  3. Khi async operation hoàn thành, continuation được schedule lại
  4. Kết quả được trả về cho caller

Lưu ý quan trọng

  • async method luôn trả về Task, Task<T>, hoặc void (chỉ dùng cho event handlers)
  • Không nên dùng .Result hoặ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

FeatureIEnumerableIAsyncEnumerable
ExecutionSynchronousAsynchronous
MemoryLoad allStream
PerformanceChậm với large dataTốt với large data
Use caseSmall datasetsLarge 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

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
FeatureIEnumerableIQueryable
ExecutionIn-memoryDatabase/Provider
Deferred Execution✅ Yes✅ Yes
Query CompositionLINQ to ObjectsLINQ to Entities/SQL
PerformanceLoad all data firstFilter at database
Use caseSmall datasets, in-memoryDatabase 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

  1. Sử dụng IQueryable thay vì IEnumerable cho database queries

    public IQueryable<Product> GetProducts() => _context.Products;
    
  2. 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
    
  3. 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ạiKhi nào dùngƯu điểmNhược điểm
    Offset-basedAdmin 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-basedInfinite scroll, mobile app, real-time feed (Facebook, Twitter)Hiệu suất cao (dùng index), không bị driftKhông nhảy page được, chỉ “next/prev”
  4. Select only needed columns

    var names = products.Select(p => p.Name).ToList(); // Not full entity
    
  5. 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();