La programmazione asincrona "cresce" attraverso la base di codice. È stato paragonato a un virus zombi . La soluzione migliore è permettergli di crescere, ma a volte ciò non è possibile.
Ho scritto alcuni tipi nella mia libreria Nito.AsyncEx per gestire una base di codice parzialmente asincrona. Non c'è soluzione che funzioni in ogni situazione, però.
Soluzione A
Se hai un metodo asincrono semplice che non ha bisogno di risincronizzarsi con il suo contesto, puoi usare Task.WaitAndUnwrapException
:
var task = MyAsyncMethod();
var result = task.WaitAndUnwrapException();
Non si desidera utilizzare Task.Wait
o Task.Result
perché racchiudono eccezioni AggregateException
.
Questa soluzione è appropriata solo se MyAsyncMethod
non si sincronizza nuovamente con il suo contesto. In altre parole, ogni await
in MyAsyncMethod
dovrebbe finire con ConfigureAwait(false)
. Ciò significa che non è possibile aggiornare alcun elemento dell'interfaccia utente o accedere al contesto della richiesta ASP.NET.
Soluzione B
Se è MyAsyncMethod
necessario sincronizzare di nuovo con il suo contesto, è possibile che sia possibile utilizzare AsyncContext.RunTask
per fornire un contesto nidificato:
var result = AsyncContext.RunTask(MyAsyncMethod).Result;
* Aggiornamento 14/04/2014: nelle versioni più recenti della libreria l'API è la seguente:
var result = AsyncContext.Run(MyAsyncMethod);
(Va bene usare Task.Result
in questo esempio perché RunTask
propagherà le Task
eccezioni).
Il motivo che potrebbe essere necessario AsyncContext.RunTask
invece Task.WaitAndUnwrapException
è a causa di una possibilità di deadlock piuttosto sottile che si verifica su WinForms / WPF / SL / ASP.NET:
- Un metodo sincrono chiama un metodo asincrono, ottenendo un
Task
.
- Il metodo sincrono attende un blocco sul
Task
.
- Il
async
metodo utilizza await
senza ConfigureAwait
.
- Non è
Task
possibile completare in questa situazione perché si completa solo al termine del async
metodo; il async
metodo non può essere completato perché sta tentando di pianificare la sua continuazione su SynchronizationContext
e WinForms / WPF / SL / ASP.NET non consentirà l'esecuzione della continuazione perché il metodo sincrono è già in esecuzione in quel contesto.
Questo è uno dei motivi per cui è una buona idea usare il più possibile ConfigureAwait(false)
in ogni async
metodo.
Soluzione C
AsyncContext.RunTask
non funzionerà in tutti gli scenari. Ad esempio, se il async
metodo attende qualcosa che richiede il completamento di un evento dell'interfaccia utente, il deadlock verrà eseguito anche con il contesto nidificato. In tal caso, è possibile avviare il async
metodo sul pool di thread:
var task = Task.Run(async () => await MyAsyncMethod());
var result = task.WaitAndUnwrapException();
Tuttavia, questa soluzione richiede MyAsyncMethod
che funzionerà nel contesto del pool di thread. Quindi non può aggiornare gli elementi dell'interfaccia utente o accedere al contesto della richiesta ASP.NET. E in tal caso, puoi anche aggiungere ConfigureAwait(false)
alle sue await
dichiarazioni e utilizzare la soluzione A.
Aggiornamento, 01/05/2019: le attuali "pratiche peggiori" sono contenute in un articolo MSDN qui .