Cuvintele cheie async și await din C# fac programarea asincronă să arate ca un cod sincron secvențial. Totuși, sub această suprafață elegantă rulează o mașină de stări complexă gestionată de .NET ThreadPool. Înțelegerea profundă a mecanismelor interne este esențială pentru a preveni blocajele fatale de producție.
Antipattern-ul Mortal: Sync-Over-Async și ThreadPool Starvation
Cea mai gravă greșeală întâlnită în codul C# este blocarea sincronă a unei operațiuni asincrone folosind .Result, .Wait() sau .GetAwaiter().GetResult():
// VULNERABIL: NICIODATĂ SĂ NU SCRIEȚI ASTFEL DE COD!
public string ObtineDateClient(long id)
{
// Firul curent din ThreadPool este BLOCAT și ținut inactiv, așteptând ca un ALT fir
// din ThreadPool să finalizeze descărcarea din rețea!
return DescarcaDateDePeReteaAsync(id).Result; // SYNC-OVER-ASYNC!
}
// CE SE ÎNTÂMPLĂ LA TRAFIC MARE:
// 1. Sosesc 500 de cereri simultane.
// 2. Toate cele 500 de fire din ThreadPool sunt blocate pe apelul .Result.
// 3. Sarcinile asincrone de rețea care ar trebui să deblocheze firele sunt puse în coadă
// deoarece NU mai există niciun fir liber în ThreadPool pentru a le executa!
// 4. Algoritmul Hill-Climbing adaugă lent doar 1 fir la fiecare 500 ms.
// REZULTAT: Aplicația îngheață complet (Deadlock masiv / Latency Spikes de zeci de secunde)!
ValueTask<T> vs. Task<T>: Când Salvăm Memorie?
Fiecare instanță de Task<T> este un obiect alocat pe Heap. Dacă o metodă asincronă returnează frecvent rezultatul direct din cache (finalizare sincronă), alocarea unui Task este o risipă inutilă de memorie:
public class ServiciuCache
{
private readonly Dictionary<string, string> _memorieLocala = new();
// Folosim ValueTask: dacă datele sunt în cache, NU se alocă nimic pe Heap (Structură pe Stivă)!
public ValueTask<string> ObtineAsync(string cheie)
{
if (_memorieLocala.TryGetValue(cheie, out string? valoare))
{
return new ValueTask<string>(valoare); // Zero heap allocation!
}
// Doar dacă lipsesc din cache apelăm metoda grea care returnează Task:
return new ValueTask<string>(DescarcaDinBazaDeDateAsync(cheie));
}
}
ConfigureAwait(false) în Biblioteci
În dezvoltarea de biblioteci (NuGet packages), folosiți întotdeauna await operatiune.ConfigureAwait(false). Acest lucru îi semnalează runtime-ului că continuarea execuției poate rula pe orice fir liber din ThreadPool, fără a forța revenirea pe contextul de sincronizare inițial (evitând deadlock-urile în aplicațiile cu interfață grafică WPF/WinForms).