Paweł Łukasiewicz: programista blogger
Paweł Łukasiewicz
2026-04-01
Paweł Łukasiewicz: programista blogger
Paweł Łukasiewicz
2026-04-01
Udostępnij Udostępnij Kontakt
Wprowadzenie

W 2015 roku async/await to była nowość (C# 5.0, 2012), ale w bardzo ograniczonej formie. W 2026 roku masz ValueTask, IAsyncEnumerable<T> oraz Parallel.ForEachAsync.

W tym wpisie poznasz Task vs ValueTask, ConfigureAwait, async streams, parallel async processing, i cancellation tokens. To fundamenty nowoczesnego C#!

📅 Timeline - ewolucja async/await
  • C# 5.0 (2012) - 🔥 async/await introduced! Task<T>
  • C# 7.0 (2017) - ValueTask<T> dla performance
  • C# 8.0 (2019) - 🔥 IAsyncEnumerable<T> - async streams!
  • C# 9.0 (2020) - Improved async patterns
  • .NET 6 (2021) - Parallel.ForEachAsync
  • C# 10+ (2021+) - Performance improvements, pooling
Problem - synchronous blocking

Synchroniczny kod - blocking threads

// Problem: synchroniczny kod blokuje thread
void DownloadFile(string url)
{
    using var client = new HttpClient();
    
    // ❌ BLOCKING - thread czeka na I/O!
    var response = client.GetAsync(url).Result;  // Blokuje thread!
    var content = response.Content.ReadAsStringAsync().Result;  // Blokuje!
    
    File.WriteAllText("file.txt", content);
    
    // Thread był zablokowany przez cały czas I/O (sekundy/minuty)!
    // W aplikacji z 100 requestami = 100 zablokowanych threadów = deadlock risk
}

// Web API endpoint - KATASTROFA
[HttpGet]
public string GetData()
{
    var data = _service.FetchFromDatabase().Result;  // ❌ Blokuje ASP.NET thread!
    return data;
    
    // ASP.NET ma ograniczoną pulę threadów (~100-200)
    // Zablokowanie threadów = aplikacja nie odpowiada!
}

Konsekwencje synchronous blocking

// Przykład: Thread pool starvation
class BadService
{
    public void ProcessRequests()
    {
        var tasks = new List<Task>();
        
        for (int i = 0; i < 1000; i++)
        {
            tasks.Add(Task.Run(() =>
            {
                // Synchroniczny I/O w thread pool!
                var result = DownloadFileSync($"http://api.com/{i}");  // ❌
                ProcessData(result);
            }));
        }
        
        Task.WaitAll(tasks.ToArray());
        
        // Problem:
        // 1. 1000 threadów z thread pool zablokowanych na I/O
        // 2. Thread pool exhaustion
        // 3. Aplikacja przestaje odpowiadać
        // 4. Deadlock risk
    }
}
🔍 Dlaczego async/await?

Synchronous I/O - thread czeka (blocked) podczas I/O operacji
Asynchronous I/O - thread jest zwolniony, może robić coś innego

W web app: 1 thread może obsłużyć 1000+ concurrent requestów dzięki async/await!

Task i Task<T> - fundamenty

Task - reprezentacja async operacji

// Task - reprezentuje operację która może się jeszcze nie zakończyć
Task task = DoSomethingAsync();

// Task ma status
Console.WriteLine(task.Status);  // Running, RanToCompletion, Faulted, Canceled

// Task<T> - Task który zwraca wartość
Task<string> taskWithResult = DownloadAsync("http://example.com");

// Await - czeka na zakończenie i pobiera wynik
string result = await taskWithResult;

async/await - podstawy

🎉 C# 5.0 - async/await

async - oznacza metodę jako asynchroniczną
await - czeka na Task bez blokowania thread

// Async method - zwraca Task
async Task DownloadFileAsync(string url)
{
    using var client = new HttpClient();
    
    // await - czeka bez blokowania thread!
    var response = await client.GetAsync(url);  // ✅ Non-blocking
    var content = await response.Content.ReadAsStringAsync();  // ✅ Non-blocking
    
    await File.WriteAllTextAsync("file.txt", content);
    
    // Thread był zwolniony podczas I/O - mógł obsługiwać inne requesty!
}

// Async method z return value
async Task<string> FetchDataAsync(string url)
{
    using var client = new HttpClient();
    var response = await client.GetAsync(url);
    return await response.Content.ReadAsStringAsync();
}

// Użycie
string data = await FetchDataAsync("http://example.com");
❌ Synchronous - blocking
string DownloadFile(string url)
{
    var client = new HttpClient();
    
    // Blokuje thread podczas I/O
    var response = client.GetAsync(url).Result;
    var content = response.Content
        .ReadAsStringAsync().Result;
    
    return content;
}

// Thread zablokowany ~1-5 sekund
// W web app: 100 requestów = deadlock
✅ Asynchronous - non-blocking
async Task<string> DownloadFileAsync(string url)
{
    var client = new HttpClient();
    
    // Thread zwolniony podczas I/O
    var response = await client.GetAsync(url);
    var content = await response.Content
        .ReadAsStringAsync();
    
    return content;
}

// Thread zwolniony - może obsługiwać inne
// W web app: 1 thread = 1000+ requestów ✨

Jak działa async/await - pod maską

// Co naprawdę robi await?
async Task<string> FetchDataAsync()
{
    var response = await httpClient.GetAsync(url);  // Punkt 1
    return await response.Content.ReadAsStringAsync();  // Punkt 2
}

// Punkt 1: await httpClient.GetAsync(url)
// 1. Wywołuje GetAsync - zwraca Task<HttpResponseMessage>
// 2. Jeśli Task NIE jest ukończony:
//    - Kompilator tworzy state machine
//    - Metoda natychmiast wraca do callera (zwraca Task)
//    - Thread jest ZWOLNIONY
//    - Continuation jest zarejestrowna (co zrobić gdy Task się ukończy)
// 3. Gdy Task się ukończy:
//    - Continuation jest wykonywana (kod po await)
//    - Może być na innym threadzie!

// Async state machine (generated by compiler)
[AsyncStateMachine(typeof(FetchDataStateMachine))]
Task<string> FetchDataAsync()
{
    var stateMachine = new FetchDataStateMachine();
    stateMachine.builder = AsyncTaskMethodBuilder<string>.Create();
    stateMachine.Start();
    return stateMachine.builder.Task;
}
ValueTask i ValueTask<T> - performance optimization

Problem z Task - heap allocation

// Task to class - każdy Task = heap allocation
async Task<int> GetCachedValueAsync(string key)
{
    // Cache hit - wartość jest już dostępna
    if (_cache.TryGetValue(key, out int value))
    {
        return value;  // ❌ Ale Task musi być alokowany na heapie!
    }
    
    // Cache miss - async fetch
    return await FetchFromDatabaseAsync(key);
}

// Problem:
// - 90% requestów to cache hit (synchronous return)
// - Ale każdy call alokuje Task na heapie
// - W hot path to miliony alokacji/s
// - GC pressure

ValueTask - zero-allocation dla sync path

🎉 C# 7.0 - ValueTask<T>

ValueTask<T> - struct, może reprezentować synchronous result BEZ alokacji!

// ValueTask - struct, nie wymaga alokacji dla sync path
async ValueTask<int> GetCachedValueAsync(string key)
{
    // Cache hit - synchronous return, ZERO allocations! ✨
    if (_cache.TryGetValue(key, out int value))
    {
        return value;  // ValueTask wraps value directly - no heap!
    }
    
    // Cache miss - async fetch
    return await FetchFromDatabaseAsync(key);  // Task is allocated here
}

// Zalety:
// ✅ Cache hit (90%) - zero allocations
// ✅ Cache miss (10%) - Task allocated (unavoidable)
// ✅ Overall - 90% reduction w allocations!

Task vs ValueTask - kiedy czego używać?

Feature Task<T> ValueTask<T>
Type Class (reference type) Struct (value type)
Heap allocation Always Only for async path
Performance Good Better (w sync path)
Można await wiele razy? ✅ Tak ❌ Tylko raz!
Można .Result? ✅ Tak ❌ Nie
Można WhenAll/WhenAny? ✅ Tak ❌ Trzeba .AsTask()
Kiedy używać Default choice Hot paths z często sync results
// ✅ Używaj ValueTask gdy:
// - Często synchronous result (cache hit, validation, etc.)
// - Hot path (wykonywane miliony razy)
// - Performance critical

// ❌ NIE używaj ValueTask gdy:
// - Zawsze async (I/O, network, database)
// - Potrzebujesz await wiele razy
// - Potrzebujesz Task.WhenAll/WhenAny
// - Nie jest performance bottleneck

// Przykład 1: Cache - DOBRZE dla ValueTask
async ValueTask<User> GetUserAsync(int id)
{
    if (_cache.TryGetValue(id, out User user))
        return user;  // Sync - zero allocations!
    
    return await _db.GetUserAsync(id);
}

// Przykład 2: Pure I/O - używaj Task
async Task<string> DownloadAsync(string url)
{
    return await _httpClient.GetStringAsync(url);  // Zawsze async - Task OK
}
⚠️ ValueTask - zasady użycia!
  • NIE await ValueTask więcej niż raz - undefined behavior!
  • NIE używaj .Result na ValueTask - exception!
  • ✅ Jeśli potrzebujesz await wiele razy - użyj .AsTask()
  • ✅ ValueTask to optimization - używaj tylko gdy profiler pokazuje problem
ValueTask<int> task = GetValueAsync();

// ❌ BŁĄD - await więcej niż raz!
int result1 = await task;
int result2 = await task;  // ❌ Undefined behavior!

// ✅ Jeśli potrzebujesz - użyj .AsTask()
Task<int> taskCopy = task.AsTask();
int result1 = await taskCopy;
int result2 = await taskCopy;  // ✅ OK
ConfigureAwait - context capturing

Problem - synchronization context capture

// W ASP.NET / WPF / WinForms - jest SynchronizationContext
// await domyślnie wraca na ORYGINALNY context

// ASP.NET example
async Task HandleRequestAsync()
{
    var data = await FetchDataAsync();  // Punkt 1
    
    // Po await - kontynuacja wraca na ASP.NET request context
    ProcessData(data);  // Ten kod MUSI być na tym samym contexcie
}

// Punkt 1: await FetchDataAsync()
// 1. Thread jest zwolniony
// 2. Task się wykonuje (może być na innym threadzie)
// 3. Task się kończy
// 4. Kontynuacja (kod po await) jest schedulowana NA ORYGINALNY CONTEXT
// 5. ASP.NET request context zabiera thread z pool
// 6. Kontynuacja jest wykonywana

// Problem w library code:
async Task LibraryMethodAsync()
{
    var result1 = await Step1Async();  // Context capture
    var result2 = await Step2Async();  // Context capture
    var result3 = await Step3Async();  // Context capture
    
    // Każdy await scheduluje continuation na oryginalny context
    // W library nie potrzebujesz context - overhead!
}

ConfigureAwait(false) - performance optimization

🎉 ConfigureAwait(false)

.ConfigureAwait(false) - NIE wracaj na oryginalny context!

// ConfigureAwait(false) - continuation może być na dowolnym threadzie
async Task LibraryMethodAsync()
{
    var result1 = await Step1Async().ConfigureAwait(false);  // Bez context
    var result2 = await Step2Async().ConfigureAwait(false);  // Bez context
    var result3 = await Step3Async().ConfigureAwait(false);  // Bez context
    
    // Kontynuacje mogą być na dowolnych threadach - szybciej!
    return ProcessResults(result1, result2, result3);
}

// Zalety:
// ✅ Performance - brak overhead schedulowania na oryginalny context
// ✅ Mniej contention - nie czeka na oryginalny thread
// ✅ Lepsze dla library code

ConfigureAwait - zasady użycia

// ✅ Używaj ConfigureAwait(false) w:
// - Library code (NIE aplikacja końcowa)
// - Kod który NIE potrzebuje oryginalnego context
// - Performance critical paths

// ❌ NIE używaj ConfigureAwait(false) w:
// - UI code (WPF, WinForms) - musisz być na UI thread
// - ASP.NET controllers - jeśli używasz HttpContext po await
// - Kod który MUSI być na oryginalnym context

// Przykład: Library (używaj ConfigureAwait)
public class HttpService
{
    public async Task<string> GetDataAsync(string url)
    {
        using var client = new HttpClient();
        var response = await client.GetAsync(url).ConfigureAwait(false);
        return await response.Content.ReadAsStringAsync().ConfigureAwait(false);
        
        // Library nie potrzebuje context - ConfigureAwait(false)!
    }
}

// Przykład: ASP.NET Controller (NIE używaj ConfigureAwait)
[ApiController]
public class UsersController : ControllerBase
{
    [HttpGet]
    public async Task<IActionResult> GetUser(int id)
    {
        var user = await _service.GetUserAsync(id);  // Bez ConfigureAwait
        
        // Po await używasz HttpContext, User, itp. - MUSISZ być na request context
        return Ok(user);
    }
}

// Przykład: WPF/WinForms (NIE używaj ConfigureAwait)
async void Button_Click(object sender, EventArgs e)
{
    var data = await LoadDataAsync();  // Bez ConfigureAwait
    
    // Po await modyfikujesz UI - MUSISZ być na UI thread
    textBox.Text = data;
}
🔍 ConfigureAwait - golden rule

Library code - ZAWSZE ConfigureAwait(false)
Application code - NIE używaj ConfigureAwait (chyba że wiesz co robisz)

🔥 IAsyncEnumerable<T> - async streams (C# 8)

Problem - zwracanie stream danych

// Przed C# 8 - musisz załadować WSZYSTKO do pamięci
async Task<List<User>> GetAllUsersAsync()
{
    var users = new List<User>();
    
    // Fetch 1 milion users
    for (int page = 0; page < 1000; page++)
    {
        var pageData = await FetchPageAsync(page);  // 1000 users
        users.AddRange(pageData);
    }
    
    return users;  // ❌ 1 milion users w pamięci!
}

// Konsument musi czekać na WSZYSTKO
var allUsers = await GetAllUsersAsync();  // Czeka na 1 milion!

foreach (var user in allUsers)
{
    ProcessUser(user);  // Mógłbyś zacząć wcześniej!
}

IAsyncEnumerable<T> - streaming data!

🎉 C# 8 - IAsyncEnumerable<T>

async IAsyncEnumerable<T> - yield return w async! Stream danych!

// C# 8 - async streams!
async IAsyncEnumerable<User> GetAllUsersAsync()
{
    for (int page = 0; page < 1000; page++)
    {
        var pageData = await FetchPageAsync(page);  // 1000 users
        
        foreach (var user in pageData)
        {
            yield return user;  // ✅ Stream jeden po drugim!
        }
    }
    
    // Nie trzeba trzymać wszystkiego w pamięci! ✨
}

// Konsument dostaje dane od razu jak są dostępne
await foreach (var user in GetAllUsersAsync())
{
    ProcessUser(user);  // Zaczyna od razu po pierwszym page!
}

// Zalety:
// ✅ Memory efficient - nie trzeba wszystkiego w pamięci
// ✅ Responsiveness - zaczyna od razu
// ✅ Backpressure - producer czeka na consumer

await foreach - consuming async streams

// await foreach - jak foreach ale dla async streams
async Task ProcessAllUsersAsync()
{
    await foreach (var user in GetUsersStreamAsync())
    {
        // Async processing
        await ProcessUserAsync(user);
        
        // Każdy element jest async-await eligible
    }
}

// WithCancellation - cancellation support
await foreach (var user in GetUsersStreamAsync().WithCancellation(cancellationToken))
{
    if (cancellationToken.IsCancellationRequested)
        break;
    
    await ProcessUserAsync(user);
}

// ConfigureAwait w async streams
await foreach (var user in GetUsersStreamAsync().ConfigureAwait(false))
{
    // Bez context capture
    await ProcessUserAsync(user);
}

Praktyczne przykłady - async streams

// Przykład 1: Streaming paginated API
async IAsyncEnumerable<Product> GetAllProductsAsync(
    [EnumeratorCancellation] CancellationToken ct = default)
{
    int page = 0;
    bool hasMore = true;
    
    while (hasMore)
    {
        ct.ThrowIfCancellationRequested();
        
        var response = await _httpClient.GetAsync($"/api/products?page={page}", ct);
        var products = await response.Content.ReadFromJsonAsync<List<Product>>(ct);
        
        if (products == null || products.Count == 0)
        {
            hasMore = false;
        }
        else
        {
            foreach (var product in products)
            {
                yield return product;
            }
            page++;
        }
    }
}

// Użycie
await foreach (var product in GetAllProductsAsync())
{
    Console.WriteLine($"{product.Name}: ${product.Price}");
}

// Przykład 2: Real-time data stream
async IAsyncEnumerable<StockPrice> GetStockPricesAsync(
    string symbol,
    [EnumeratorCancellation] CancellationToken ct = default)
{
    while (!ct.IsCancellationRequested)
    {
        var price = await FetchCurrentPriceAsync(symbol);
        yield return price;
        
        await Task.Delay(1000, ct);  // Poll every second
    }
}

// Użycie
await foreach (var price in GetStockPricesAsync("AAPL", cts.Token))
{
    Console.WriteLine($"AAPL: ${price.Value}");
    
    if (price.Value > 200)
        break;  // Stop when price > $200
}

// Przykład 3: Database streaming
async IAsyncEnumerable<Order> GetLargeResultSetAsync()
{
    await using var connection = new SqlConnection(_connectionString);
    await connection.OpenAsync();
    
    var command = new SqlCommand("SELECT * FROM Orders", connection);
    await using var reader = await command.ExecuteReaderAsync();
    
    while (await reader.ReadAsync())
    {
        yield return new Order
        {
            Id = reader.GetInt32(0),
            CustomerId = reader.GetInt32(1),
            Total = reader.GetDecimal(2)
        };
    }
}
Parallel.ForEachAsync - concurrent async operations

Problem - parallel async processing

// Chcesz przetworzyć wiele items async, równolegle

// ❌ Opcja 1: Sequential - wolno
async Task ProcessSequentialAsync(List<string> urls)
{
    foreach (var url in urls)
    {
        await DownloadAsync(url);  // Jeden po drugim - wolno!
    }
}
// 100 URLs * 1s każdy = 100 sekund! 😱

// ❌ Opcja 2: Task.WhenAll - brak kontroli nad concurrency
async Task ProcessAllAtOnceAsync(List<string> urls)
{
    var tasks = urls.Select(url => DownloadAsync(url));
    await Task.WhenAll(tasks);  // Wszystkie naraz - może być za dużo!
}
// 10,000 URLs = 10,000 concurrent connections - server overwhelmed! 💥

Parallel.ForEachAsync - controlled concurrency!

🎉 .NET 6 - Parallel.ForEachAsync

Parallel.ForEachAsync - parallel async z kontrolowaną concurrency!

// .NET 6+ - Parallel.ForEachAsync z MaxDegreeOfParallelism
async Task ProcessParallelAsync(List<string> urls)
{
    var options = new ParallelOptions
    {
        MaxDegreeOfParallelism = 10  // Max 10 concurrent operations
    };
    
    await Parallel.ForEachAsync(urls, options, async (url, ct) =>
    {
        await DownloadAsync(url, ct);
    });
}

// 100 URLs, 10 concurrent = ~10 sekund (zamiast 100!)
// Kontrolowana concurrency - nie overwhelm servera! ✨

Parallel.ForEachAsync - praktyczne przykłady

// Przykład 1: Download wiele plików
async Task DownloadFilesAsync(List<string> urls, CancellationToken ct)
{
    var options = new ParallelOptions
    {
        MaxDegreeOfParallelism = 5,
        CancellationToken = ct
    };
    
    await Parallel.ForEachAsync(urls, options, async (url, token) =>
    {
        Console.WriteLine($"Downloading {url}...");
        await DownloadFileAsync(url, token);
        Console.WriteLine($"Downloaded {url}");
    });
}

// Przykład 2: Process orders concurrently
async Task ProcessOrdersAsync(List<Order> orders)
{
    var options = new ParallelOptions
    {
        MaxDegreeOfParallelism = Environment.ProcessorCount
    };
    
    await Parallel.ForEachAsync(orders, options, async (order, ct) =>
    {
        await ValidateOrderAsync(order, ct);
        await ChargePaymentAsync(order, ct);
        await SendEmailAsync(order, ct);
    });
}

// Przykład 3: Async enumerable source
async Task ProcessStreamAsync(IAsyncEnumerable<User> users)
{
    var options = new ParallelOptions
    {
        MaxDegreeOfParallelism = 10
    };
    
    await Parallel.ForEachAsync(users, options, async (user, ct) =>
    {
        await UpdateUserAsync(user, ct);
    });
}

// Przykład 4: With progress reporting
async Task ProcessWithProgressAsync(List<string> items)
{
    int completed = 0;
    int total = items.Count;
    
    await Parallel.ForEachAsync(items, async (item, ct) =>
    {
        await ProcessItemAsync(item, ct);
        
        int current = Interlocked.Increment(ref completed);
        Console.WriteLine($"Progress: {current}/{total}");
    });
}

Parallel.ForEachAsync vs Task.WhenAll

Feature Parallel.ForEachAsync Task.WhenAll
Concurrency control ✅ MaxDegreeOfParallelism ❌ Wszystkie naraz
Memory usage Lower (controlled) Higher (wszystkie tasks)
Server friendly ✅ Nie overwhelm ❌ Może overwhelm
Kiedy używać Dużo items, ograniczona concurrency Mało items, wszystkie naraz OK
CancellationToken - canceling async operations

Problem - nie można zatrzymać async operations

// Bez cancellation - nie można zatrzymać
async Task LongRunningOperationAsync()
{
    for (int i = 0; i < 1000; i++)
    {
        await ProcessItemAsync(i);
        // Użytkownik kliknął "Cancel" - ale nie możesz zatrzymać! 😱
    }
}

// User experience:
// 1. User klika "Start"
// 2. Operacja trwa 10 minut
// 3. User klika "Cancel"
// 4. Nic się nie dzieje - operacja dalej trwa
// 5. User frustrated 😤

CancellationToken - cooperative cancellation

// CancellationToken - cooperative cancellation
async Task LongRunningOperationAsync(CancellationToken ct)
{
    for (int i = 0; i < 1000; i++)
    {
        // Check if cancellation requested
        ct.ThrowIfCancellationRequested();  // Throws OperationCanceledException
        
        await ProcessItemAsync(i, ct);
    }
}

// Użycie - CancellationTokenSource
var cts = new CancellationTokenSource();

// Start operation
var task = LongRunningOperationAsync(cts.Token);

// User klika "Cancel"
cts.Cancel();  // Sygnalizuje cancellation

try
{
    await task;
}
catch (OperationCanceledException)
{
    Console.WriteLine("Operation canceled by user");
}

// User experience:
// 1. User klika "Start"
// 2. Operacja zaczyna
// 3. User klika "Cancel"
// 4. Operacja NATYCHMIAST się kończy ✨
// 5. User happy 😊

CancellationToken - best practices

// Pattern 1: ThrowIfCancellationRequested
async Task ProcessAsync(CancellationToken ct)
{
    for (int i = 0; i < items.Count; i++)
    {
        ct.ThrowIfCancellationRequested();  // Throw if canceled
        await ProcessItemAsync(items[i], ct);
    }
}

// Pattern 2: IsCancellationRequested (no throw)
async Task ProcessAsync(CancellationToken ct)
{
    for (int i = 0; i < items.Count; i++)
    {
        if (ct.IsCancellationRequested)
        {
            Console.WriteLine("Cancellation requested");
            return;  // Graceful exit
        }
        
        await ProcessItemAsync(items[i], ct);
    }
}

// Pattern 3: Register callback
async Task ProcessAsync(CancellationToken ct)
{
    using (ct.Register(() => Console.WriteLine("Canceling...")))
    {
        await LongOperationAsync(ct);
    }
}

// Pattern 4: Timeout
var cts = new CancellationTokenSource(TimeSpan.FromSeconds(30));  // 30s timeout

try
{
    await ProcessAsync(cts.Token);
}
catch (OperationCanceledException)
{
    Console.WriteLine("Timeout!");
}

// Pattern 5: Linked tokens (combine multiple)
var cts1 = new CancellationTokenSource();  // User cancellation
var cts2 = new CancellationTokenSource(TimeSpan.FromSeconds(60));  // Timeout

var linkedCts = CancellationTokenSource.CreateLinkedTokenSource(
    cts1.Token, 
    cts2.Token
);

// Canceled gdy KTÓRYKOLWIEK się cancel
await ProcessAsync(linkedCts.Token);

CancellationToken - praktyczne przykłady

// Przykład 1: HTTP request z cancellation
async Task<string> DownloadAsync(string url, CancellationToken ct)
{
    using var client = new HttpClient();
    
    // HttpClient respects CancellationToken!
    var response = await client.GetAsync(url, ct);
    return await response.Content.ReadAsStringAsync(ct);
}

// Przykład 2: Async foreach z cancellation
async Task ProcessUsersAsync(CancellationToken ct)
{
    await foreach (var user in GetUsersAsync(ct))
    {
        ct.ThrowIfCancellationRequested();
        await ProcessUserAsync(user, ct);
    }
}

// Przykład 3: Scenariusz na UI (WPF/Blazor)
class ViewModel
{
    private CancellationTokenSource? _cts;
    
    public async Task StartAsync()
    {
        _cts = new CancellationTokenSource();
        
        try
        {
            await LongOperationAsync(_cts.Token);
        }
        catch (OperationCanceledException)
        {
            Console.WriteLine("Canceled");
        }
    }
    
    public void Cancel()
    {
        _cts?.Cancel();
    }
}

// Przykład 4: ASP.NET with request timeout
[HttpGet]
public async Task<IActionResult> GetData(CancellationToken ct)
{
    // ASP.NET automatically cancels when request is aborted
    var data = await _service.FetchDataAsync(ct);
    return Ok(data);
}
Podsumowanie

Async/Await - fundamenty nowoczesnego C#!

  • Problem synchronous blocking - thread starvation, deadlocks
  • Task i Task<T> - reprezentacja async operations
  • async/await - non-blocking, thread reuse
  • ValueTask<T> - zero-allocation dla sync paths
  • ConfigureAwait - context capturing, library optimization
  • 🔥 IAsyncEnumerable<T> - async streams, yield return
  • await foreach - consuming async streams
  • Parallel.ForEachAsync - controlled concurrency
  • CancellationToken - cooperative cancellation, timeouts

W kolejnym wpisie poznasz LINQ - query syntax, method chaining, deferred execution, custom operators!

Zadanie dla Ciebie 🎯

Stwórz async downloader z progress reporting i cancellation:

// Interface
interface IDownloader
{
    Task<DownloadResult> DownloadAsync(
        string url,
        IProgress<DownloadProgress> progress,
        CancellationToken ct);
}

record DownloadProgress(long BytesDownloaded, long TotalBytes, double Percentage);
record DownloadResult(bool Success, string FilePath, TimeSpan Duration);

// Użycie:
var downloader = new Downloader();
var cts = new CancellationTokenSource();
var progress = new Progress<DownloadProgress>(p =>
    Console.WriteLine($"{p.Percentage:F1}%"));

var result = await downloader.DownloadAsync(
    "http://example.com/large-file.zip",
    progress,
    cts.Token);

Wymagania:

  1. async/await dla I/O operations
  2. Progress reporting co 1% lub co 1MB
  3. CancellationToken support
  4. Retry logic (3 próby)
  5. Timeout 30 sekund
🎯 BONUS: Async Web Crawler

Stwórz web crawler używając WSZYSTKICH async patterns!

Do zaimplementowania:

  1. Crawler core:
    • async IAsyncEnumerable<Page> CrawlAsync(string startUrl, int maxDepth)
    • Parallel.ForEachAsync dla concurrent crawling
    • MaxDegreeOfParallelism = 10
    • Depth-first lub breadth-first traversal
  2. Performance optimizations:
    • ValueTask dla already-visited URLs (cache hit)
    • ConfigureAwait(false) w library code
    • Connection pooling (HttpClient singleton)
  3. Cancellation support:
    • CancellationToken propagation
    • Graceful shutdown
    • Timeout per page (5 sekund)
  4. Progress reporting:
    • IProgress<CrawlProgress>
    • Real-time stats (pages/s, errors, depth)
  5. Features:
    • Robots.txt respecting
    • URL deduplication
    • Error handling & retry
    • Domain filtering

Przykład użycia:

var crawler = new WebCrawler();
var cts = new CancellationTokenSource(TimeSpan.FromMinutes(5));

var progress = new Progress<CrawlProgress>(p =>
    Console.WriteLine($"Pages: {p.PagesProcessed}, Depth: {p.CurrentDepth}, Errors: {p.Errors}"));

var options = new CrawlOptions
{
    MaxDepth = 3,
    MaxConcurrency = 10,
    AllowedDomains = new[] { "example.com" },
    RespectRobotsTxt = true
};

await foreach (var page in crawler.CrawlAsync(
    "https://example.com", 
    options,
    progress, 
    cts.Token))
{
    Console.WriteLine($"Crawled: {page.Url} ({page.Links.Count} links)");
    
    // Process page content
    await ProcessPageAsync(page, cts.Token);
}

Console.WriteLine("Crawl completed!");

Ten projekt demonstruje pełną moc async/await - IAsyncEnumerable, Parallel.ForEachAsync, ValueTask, CancellationToken, wszystko razem! 🚀🕷️