Programmazione asincrona con async e await
Finora abbiamo visto diversi sistemi per realizzare codice multithread, in grado cioè di essere eseguito su più thread contemporaneamente. Un particolare effetto dello sviluppo multithread è costituito dall'esecuzione asincrona di un metodo, in cui cioè il thread che effettua la chiamata non resta bloccato in attesa del completamento del metodo invocato.
I vantaggi della programmazione asincrona si notano soprattutto nell'ambito di applicazioni Windows Store o WPF, dato che in questi casi il thread chiamante è quasi sempre quello dell'interfaccia utente: metodi che durano a lungo, se non sono eseguiti in maniera asincrona, bloccano l'interfaccia e quindi l'applicazione non è più responsiva, mentre ciò non accade nel caso di esecuzione asincrona. Tuttavia, anche in altri ambiti, quali per esempio quello delle applicazioni web, lo sviluppo asincrono presenta immensi vantaggi, perché consente in ogni caso di liberare un thread che può essere sfruttato dal server per servire un'altra richiesta.
Nonostante il modello dei Task visto finora sia sicuramente uno dei sistemi più avanzati e intuitivi per scrivere codice asincrono, è innegabile che il codice risultante sia sicuramente più complesso e meno leggibile.
Ciò può rappresentare un problema quando la logica da implementare sia complicata e, per certi versi, ha sempre rappresentato una delle difficoltà principali all'adozione della programmazione asincrona. C# 5 semplifica di molto questo scenario grazie all'introduzione di due nuove parole chiave, async e await. Cerchiamo di chiarirne il funzionamento con un esempio.
Public Sub Download()
Dim client As WebRequest =
HttpWebRequest.Create("http://www.google.com")
Dim response = client.GetResponse()
Using reader As New StreamReader(response.GetResponseStream)
' esecuzione sincrona della richiesta
Dim result = reader.ReadToEnd()
Console.WriteLine(result)
End Using
End Sub
public static void Download()
{
Console.WriteLine("---- Download ----");
WebRequest client = HttpWebRequest.Create("http://www.google.com");
var response = client.GetResponse();
using (var reader = new StreamReader(response.GetResponseStream()))
{
// esecuzione sincrona della richiesta
var result = reader.ReadToEnd();
Console.WriteLine(result);
}
}
Il codice dell'esempio 1 sfrutta la classe WebRequest e, successivamente, StreamReader, per recuperare il contenuto dell'URL specificato e, fintanto che il risultato non viene recuperato, il thread resta bloccato e in attesa. Sfruttando async e await possiamo realizzarne la versione sincrona in maniera estremamente semplice.
Public Async Function DownloadAsync() As Task
Dim client As WebRequest =
HttpWebRequest.Create("http://www.google.com")
Dim response = client.GetResponse()
Using reader As New StreamReader(response.GetResponseStream)
' esecuzione asincrona della richiesta
Dim result = Await reader.ReadToEndAsync()
Console.WriteLine(result)
End Using
End Function
public static async Task DownloadAsync()
{
Console.WriteLine("---- DownloadAsync ----");
WebRequest client = HttpWebRequest.Create("http://www.google.com");
var response = client.GetResponse();
using (var reader = new StreamReader(response.GetResponseStream()))
{
// esecuzione asincrona della richiesta
var result = await reader.ReadToEndAsync();
Console.WriteLine(result);
}
}
Questa nuova versione del metodo ha richiesto intanto di specificare la keyword async nella sua dichiarazione, mentre il tipo restituito è variato da void a Task. Si tratta di una direttiva che indica al compilatore la nostra intenzione di gestire all'interno di questo metodo delle chiamate asincrone. Il passo successivo è stato quello di utilizzare il metodo ReadToEndAsync, che rappresenta la variante asincrona di ReadToEnd: esso, infatti, restituisce un oggetto di tipo Task<string> ed esegue l'operazione di download in un altro thread.
Con il .NET Framework 4.5, la maggior parte dei metodi la cui durata può essere potenzialmente "lunga" possiede una variante asincrona. Anche l'aggiunta di riferimenti a web service esterni consente di generare metodi asincroni. Se, per un particolare caso, il metodo asincrono non risulta disponibile, è sufficiente eseguirlo all'interno di un Task per poter sfruttare comunque la tecnica che abbiamo mostrato.
In condizioni normali, dovremmo in qualche modo gestire il callback su questo oggetto per recuperare l'informazione prelevata dalla rete; grazie alla parola chiave await, invece, è il compilatore stesso a preoccuparsi di iniettare tutto il codice necessario, e noi possiamo semplicemente limitarci a prelevare il risultato dell'esecuzione asincrona e assegnarlo alla variabile result di tipo string, senza curarci del fatto che in realtà il metodo invocato restituisca un Task.
Il vantaggio, ovviamente, è che pur sfruttando le potenzialità e le caratteristiche della programmazione asincrona, il codice che siamo chiamati a scrivere è assolutamente analogo alla versione sincrona dell'esempio 1.