async/await, Task i CancellationToken
Asynchroniczność w .NET to potężne narzędzie, ale ma swoje pułapki: deadlocki
z .Result, brak anulowania, fałszywe async void. Ta lekcja zbiera house rules
ProfessNet dla kodu async w C#.
Task i async/await
Metoda async zwraca Task (bez wyniku) lub Task<T> (z wynikiem). await
nie blokuje wątku — zwalnia go na czas operacji I/O.
public async Task<ScanResult> RunScanAsync(string forest)
{
var hosts = await _repository.GetHostsAsync(forest);
var result = await _scanner.ScanAsync(hosts);
return result;
}
Nigdy .Result ani .Wait()
Blokowanie na asynchronicznym kodzie powoduje deadlocki (zwłaszcza w kontekście ASP.NET) i marnuje wątki.
// źle — ryzyko deadlocku, blokuje wątek
var result = RunScanAsync(forest).Result;
// dobrze — await aż do góry
var result = await RunScanAsync(forest);
Standard ProfessNet: zasada „async all the way". Jeśli metoda woła coś asynchronicznego, sama jest
asynci propaguje to do wywołującego..Resulti.Wait()są zakazane w kodzie aplikacyjnym.
CancellationToken propaguje się wszędzie
Każda publiczna metoda async przyjmuje CancellationToken i przekazuje go dalej.
Dzięki temu długie operacje da się anulować (zamknięcie żądania, timeout).
public async Task<ScanResult> RunScanAsync(
string forest,
CancellationToken cancellationToken = default)
{
var hosts = await _repository.GetHostsAsync(forest, cancellationToken);
cancellationToken.ThrowIfCancellationRequested();
return await _scanner.ScanAsync(hosts, cancellationToken);
}
W ASP.NET Core token dostajesz z parametru akcji:
[HttpPost("scan")]
public async Task<IActionResult> Scan(ScanRequest req, CancellationToken ct)
{
var result = await _service.RunScanAsync(req.Forest, ct);
return Accepted(result);
}
Standard ProfessNet:
CancellationTokento ostatni parametr metody async, domyślniedefault. Przekazujemy go do każdego kolejnego wywołania async.
async void tylko w event handlerach
async void nie da się await-ować ani złapać wyjątku — to źródło cichych
awarii. Wszędzie poza handlerami zdarzeń używamy Task.
// źle — wyjątek zniknie, nie da się poczekać
public async void Process() { await DoWork(); }
// dobrze
public async Task ProcessAsync() { await DoWork(); }
Równoległość: Task.WhenAll
Niezależne operacje uruchamiamy współbieżnie.
var tasks = forests.Select(f => ScanForestAsync(f, ct));
ScanResult[] results = await Task.WhenAll(tasks);
| Konstrukcja | Kiedy |
|---|---|
await | pojedyncza operacja async |
Task.WhenAll | wiele operacji współbieżnie |
CancellationToken | każda publiczna metoda async |
ConfigureAwait(false) | kod bibliotek (bez kontekstu UI/żądania) |
Wskazówka: w bibliotekach (nie w ASP.NET) używaj
ConfigureAwait(false), żeby nie wracać na oryginalny kontekst synchronizacji — to drobny zysk wydajności i ochrona przed deadlockami.
Async all the way, zero .Result, token anulowania w każdej metodzie. Trzymaj
się tego, a kod async w .NET będzie wydajny, anulowalny i wolny od deadlocków.