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.Waito Task.Resultperché racchiudono eccezioni AggregateException.
Questa soluzione è appropriata solo se MyAsyncMethodnon si sincronizza nuovamente con il suo contesto. In altre parole, ogni awaitin MyAsyncMethoddovrebbe 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 è MyAsyncMethodnecessario sincronizzare di nuovo con il suo contesto, è possibile che sia possibile utilizzare AsyncContext.RunTaskper 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.Resultin questo esempio perché RunTaskpropagherà le Taskeccezioni).
Il motivo che potrebbe essere necessario AsyncContext.RunTaskinvece 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
asyncmetodo utilizza awaitsenza ConfigureAwait.
- Non è
Taskpossibile completare in questa situazione perché si completa solo al termine del asyncmetodo; il asyncmetodo non può essere completato perché sta tentando di pianificare la sua continuazione su SynchronizationContexte 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 asyncmetodo.
Soluzione C
AsyncContext.RunTasknon funzionerà in tutti gli scenari. Ad esempio, se il asyncmetodo 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 asyncmetodo sul pool di thread:
var task = Task.Run(async () => await MyAsyncMethod());
var result = task.WaitAndUnwrapException();
Tuttavia, questa soluzione richiede MyAsyncMethodche 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 awaitdichiarazioni e utilizzare la soluzione A.
Aggiornamento, 01/05/2019: le attuali "pratiche peggiori" sono contenute in un articolo MSDN qui .