Best practice nella programmazione asincrona .Net

La programmazione asincrona consente di eseguire operazioni di I / O a uso intensivo di risorse senza dover bloccare il thread principale o in esecuzione dell'applicazione. Sebbene vantaggioso e apparentemente facile da implementare, presenta molta complessità e rischi. I potenziali rischi associati alla programmazione asincrona, in particolare utilizzando la programmazione asincrona nel modo sbagliato non seguendo le pratiche consigliate, includono deadlock, arresti anomali dei processi e persino prestazioni lente. Dovresti anche essere abile nella scrittura, nel debug del codice asincrono.

Evita di avere un tipo di ritorno void nei metodi asincroni

Un metodo in C # viene reso un metodo asincrono usando la parola chiave async nella firma del metodo. Puoi avere una o più parole chiave in attesa all'interno di un metodo asincrono. La parola chiave await viene utilizzata per indicare il punto di sospensione. Un metodo asincrono in C # può avere uno qualsiasi di questi tipi restituiti: Task, Task e void. La parola chiave "await" viene utilizzata in un metodo asincrono per informare il compilatore che il metodo può avere un punto di sospensione e di ripresa.

Si noti che quando si utilizza la TPL, l'equivalente della restituzione di void in TPL è Async Task. Tieni presente che il vuoto asincrono è e deve essere utilizzato solo per eventi asincroni. Se lo usi altrove, potresti incorrere in errori. In altre parole, un metodo asincrono con void come tipo restituito non è consigliato. perché i metodi asincroni che restituiscono void hanno una semantica diversa quando lavori con le eccezioni nella tua applicazione.

Quando si verifica un'eccezione in un metodo asincrono che ha un tipo restituito Task o Task, l'oggetto eccezione viene archiviato all'interno dell'oggetto Task. Al contrario, se hai un metodo asincrono con un tipo di ritorno void, non è associato alcun oggetto Task. Tali eccezioni vengono generate nel SynchronizationContext che era attivo al momento in cui è stato chiamato il metodo asincrono. In altre parole, non è possibile gestire le eccezioni sollevate all'interno di un metodo void asincrono utilizzando gestori di eccezioni scritti all'interno del metodo asincrono. Anche i metodi asincroni che hanno un tipo restituito void sono difficili da testare a causa di questa differenza nella semantica di gestione degli errori. Per tua informazione, la classe SynchronizationContext nello spazio dei nomi System.Threading rappresenta un contesto di sincronizzazione in .Net e ti aiuta a mettere in coda un'attività in un altro contesto.

Il listato di codice seguente lo illustra. Hai due metodi, ovvero Test e TestAsync e il secondo genera un'eccezione.

public class AsyncDemo

   {

       public void Test()

       {

           try

           {

               TestAsync();

           }

           catch (Exception ex)

           {

               Console.WriteLine(ex.Message);

           }

       }

       private async void TestAsync()

       {

           throw new Exception("This is an error message");

       }

   }

Ecco come creare un'istanza della classe AsyncDemo e richiamare il metodo Test.

static void Main(string[] args)

       {

           AsyncDemo obj = new AsyncDemo();

           obj.Test();

           Console.Read();

       }

Il metodo di test effettua una chiamata al metodo TestAsync e la chiamata viene racchiusa in un blocco try-catch con l'intento di gestire l'eccezione generata all'interno del metodo TestAsync. Tuttavia, l'eccezione generata all'interno del metodo TestAsync non verrà mai rilevata, ovvero gestita all'interno del metodo chiamante Test.

Evita di mischiare codice asincrono e sincrono

Non dovresti mai avere una combinazione di codice sincrono e asincrono. È una cattiva pratica di programmazione bloccare il codice asincrono effettuando chiamate a Task.Wait o Task.Result. Consiglierei di utilizzare codice asincrono end-to-end: è il modo più sicuro per evitare che gli errori si insinuino.

È possibile evitare deadlock utilizzando. ConfigureAwait(continueOnCapturedContext: false)ogni volta che fai una chiamata in attesa. Se non lo usi, il metodo async si bloccherà nel punto in cui è stato chiamato await. In questo caso stai solo informando il cameriere di non catturare il contesto corrente. Direi che è una buona pratica usare .ConfigureAwait (false) a meno che tu non abbia un motivo specifico per non usarlo.

Discuterei di più sulla programmazione asincrona nei miei futuri post sul blog qui. Per ulteriori informazioni sulle procedure consigliate nella programmazione asincrona, fare riferimento all'ottimo articolo di Stephen Cleary su MSDN.