1. Introduzione
Immagina che il tuo codice sia una fila al supermercato. Se avvii un metodo sincrono, tutta la coda aspetta che il cassiere finisca con un cliente prima di passare al prossimo. Se invece il metodo è asincrono, metti la tua spesa al banco e vai a fare altro; quando il cassiere ha finito, ti chiama.
Un metodo asincrono (metodo async) in .NET è un metodo normale con una speciale "etichetta" davanti (async) che permette di usare dentro l'operatore await per attendere operazioni asincrone (per esempio I/O di rete o file) senza bloccare il thread principale. È proprio questa "etichetta" che trasforma il noioso negozio sincrono in un servizio super efficiente senza file.
Firma base di un metodo asincrono
Un metodo asincrono deve essere dichiarato con il modificatore async nella firma. Ecco l'esempio più semplice:
public async Task MyAsyncMethod()
{
// Il tuo codice asincrono qui
}
Tipi di ritorno dei metodi asincroni:
| Tipo di ritorno | Descrizione | Esempio |
|---|---|---|
|
Il metodo esegue lavoro in modo asincrono, ma non ritorna un risultato | |
|
Il metodo esegue lavoro asincrono e ritorna un risultato di tipo T | |
|
Usato solo per eventi. Non consigliato altrove! | |
|
Per scenari ad alte prestazioni, quando il risultato è spesso già disponibile sincronicamente | — |
Importante: Non usare async void tranne che per i gestori di eventi! Altrimenti rischi di perdere la gestione corretta delle eccezioni.
2. Esempio semplice di metodo asincrono
Torniamo alla nostra applicazione di esempio (una semplice console app) e aggiungiamo un metodo asincrono che ottiene dati dalla rete. Emuleremo un ritardo.
using System;
using System.Threading.Tasks;
class Program
{
static async Task Main(string[] args)
{
Console.WriteLine("Avvio del metodo asincrono...");
await MyFakeDownloadAsync();
Console.WriteLine("Operazione asincrona completata!");
}
static async Task MyFakeDownloadAsync()
{
Console.WriteLine("Iniziamo il download (emuliamo un ritardo di 2 sec)...");
await Task.Delay(2000); // Emuliamo un'operazione lunga
Console.WriteLine("Download completato!");
}
}
Commenti sull'esempio:
- La parola chiave async è presente in Main e in MyFakeDownloadAsync.
- L'operatore await può essere usato solo dentro metodi marcati con async.
- Il metodo Task.Delay(2000) emula un'operazione lunga (per esempio una chiamata di rete).
- Dopo l'uso di await il thread non viene bloccato — l'esecuzione di Main "si sospende" fino al completamento del delay.
3. Come funziona il metodo asincrono sotto il cofano?
Quando il compilatore trova un metodo con la parola chiave async, lo trasforma in una "state machine" che memorizza il punto di sospensione, le variabili locali e continua l'esecuzione dopo il completamento dell'operazione awaited.
Ecco uno schema molto semplificato:
+-----------------------------------------------------------+
| Chiamata del metodo asincrono (es. await X) |
+-------------------------+---------------------------------+
|
v
L'operazione è completa? (non Task.Completed)
| |
| sì | no
v v
Sospendere Restituire
l'esecuzione, subito
ricordare il il risultato
contesto
|
L'operazione asincrona termina...
|
v
La state machine "sveglia" il metodo,
l'esecuzione continua dal punto dopo await
Non serve memorizzare ogni dettaglio: il contesto (punto di sospensione e variabili locali) viene salvato e ripristinato automaticamente quando l'operazione termina.
Dove si può (e non si può) usare async e await
- async si mette davanti alla dichiarazione del metodo (o della lambda).
- Dentro un metodo async si può (e si deve) usare await.
- Se provi a usare await in un metodo senza async — otterrai un errore di compilazione.
- Se dichiari async ma nel metodo non c'è nessun await — riceverai un warning; il metodo verrà eseguito in modo sincrono e restituirà un Task già completato.
4. Varianti di valori di ritorno e loro caratteristiche
Risultato Task
Se il metodo esegue azioni asincrone ma non ritorna un valore, usa Task:
public async Task SaveToFileAsync(string path)
{
await Task.Delay(1000);
// Salviamo qualcosa su file
}
Risultato Task<T>
Se serve un risultato, usa Task<T>:
public async Task<int> CalculateAsync()
{
await Task.Delay(500);
return 42;
}
Chiamata:
int result = await CalculateAsync();
Risultato async void
Usalo solo per eventi! Per esempio, un handler di click:
private async void Button_Click(object sender, EventArgs e)
{
await Task.Delay(1000);
// Facciamo qualcos'altro
}
Perché non usare async void nei metodi normali? Perché non sapresti quando quel metodo termina e non potresti gestire correttamente le eccezioni al suo interno.
Risultati ValueTask e ValueTask<T>
Sono nati per librerie ad alte prestazioni, quando il risultato è spesso già pronto in modo sincrono — evitano allocazioni extra di Task. All'inizio della carriera puoi non usarli: non sono così comuni.
5. Esempio: calcolatore asincrono che ritorna un risultato
Supponiamo che il programma calcoli la somma di due numeri "con ritardo", come se fosse un'operazione molto complessa:
using System;
using System.Threading.Tasks;
class Program
{
static async Task Main(string[] args)
{
Console.WriteLine("Inserisci il primo numero:");
int a = int.Parse(Console.ReadLine()!);
Console.WriteLine("Inserisci il secondo numero:");
int b = int.Parse(Console.ReadLine()!);
Console.WriteLine("Eseguiamo il calcolo...");
int sum = await CalculateSumAsync(a, b);
Console.WriteLine($"Somma: {sum}");
}
static async Task<int> CalculateSumAsync(int x, int y)
{
await Task.Delay(2000); // Operazione "lunga"
return x + y;
}
}
Cosa è importante qui:
- CalculateSumAsync è un metodo asincrono che ritorna Task<int> e contiene await.
- La chiamata in Main usa await (promemoria: da C# 7.1 il metodo Main può essere asincrono).
- Mentre la somma viene calcolata, il thread non è bloccato — puoi mostrare progress o fare altro lavoro.
6. Metodi annidati e await annidati
Un metodo asincrono può chiamare un altro metodo asincrono, che a sua volta può chiamarne un altro. L'intera catena continua automaticamente:
public async Task<int> StartWorkAsync()
{
int data = await GetDataAsync();
int result = await ProcessDataAsync(data);
return result;
}
È semplice: aspetti il completamento di un'operazione, poi dell'altra.
7. Trappole e errori comuni
1. Non dimenticare async!
Se scordi async, il compilatore si arrabbierà se trova await nel metodo e non ti farà compilare.
2. Non usare async void nei metodi normali
I metodi asincroni di tipo void (tranne i gestori di eventi) sono buchi neri per le eccezioni. Gli errori al loro interno possono "annegare" e tu non lo saprai.
3. Metodo asincrono SENZA await
Si può fare, ma è inutile — il metodo verrà eseguito in modo sincrono e restituirà un Task già completato.
public async Task DoNothingAsync()
{
// Ops, nessun await!
}
4. Restituire Task da un metodo NON-asincrono
Si fa per uniformare le interfacce: alcune operazioni sono realmente asincrone, altre sono istantanee.
public Task<int> Foo()
{
// return 42; // Non si può così!
return Task.FromResult(42); // Valido, ma non c'è asincronicità.
}
GO TO FULL VERSION