Programowanie asynchroniczne „rośnie” poprzez bazę kodu. Został porównany do wirusa zombie . Najlepszym rozwiązaniem jest umożliwienie jej wzrostu, ale czasami jest to niemożliwe.
Napisałem kilka typów w mojej bibliotece Nito.AsyncEx do radzenia sobie z częściowo asynchroniczną bazą kodu. Jednak nie ma rozwiązania, które działałoby w każdej sytuacji.
Rozwiązanie A
Jeśli masz prostą metodę asynchroniczną, która nie wymaga synchronizacji z powrotem do swojego kontekstu, możesz użyć Task.WaitAndUnwrapException:
var task = MyAsyncMethod();
var result = task.WaitAndUnwrapException();
Zdajesz nie chcą używać Task.Waitalbo Task.Resultponieważ owinąć wyjątki AggregateException.
To rozwiązanie jest odpowiednie tylko wtedy, MyAsyncMethodgdy nie synchronizuje się z kontekstem. Innymi słowy, każdy awaitw MyAsyncMethodpowinny kończyć ConfigureAwait(false). Oznacza to, że nie może zaktualizować żadnych elementów interfejsu użytkownika ani uzyskać dostępu do kontekstu żądania ASP.NET.
Rozwiązanie B
Jeśli MyAsyncMethodkonieczne jest zsynchronizowanie z powrotem do kontekstu, możesz użyć opcji, AsyncContext.RunTaskaby zapewnić zagnieżdżony kontekst:
var result = AsyncContext.RunTask(MyAsyncMethod).Result;
* Aktualizacja 14.04.2014: W nowszych wersjach biblioteki interfejs API wygląda następująco:
var result = AsyncContext.Run(MyAsyncMethod);
(W Task.Resulttym przykładzie można używać, ponieważ RunTaskbędą propagować Taskwyjątki).
AsyncContext.RunTaskZamiast tego możesz potrzebować Task.WaitAndUnwrapExceptionraczej subtelnej impasu, która zdarza się w WinForms / WPF / SL / ASP.NET:
- Metoda synchroniczna wywołuje metodę asynchroniczną, uzyskując
Task.
- Metoda synchroniczna blokuje oczekiwanie na
Task.
asyncMetoda wykorzystuje awaitbez ConfigureAwait.
- Nie
Taskmożna ukończyć w tej sytuacji, ponieważ kończy się dopiero po zakończeniu asyncmetody; asyncmetoda może nie jest kompletna, ponieważ stara się zaplanować swoją kontynuację do SynchronizationContexti WinForms / WPF / SL / ASP.NET nie pozwoli na kontynuację uruchomić ponieważ metoda synchroniczna jest już uruchomiony w tym kontekście.
Jest to jeden z powodów, dla których warto stosować ConfigureAwait(false)każdą asyncmetodę w jak największym stopniu.
Rozwiązanie C
AsyncContext.RunTasknie będzie działać w każdym scenariuszu. Na przykład, jeśli asyncmetoda czeka na coś, co wymaga zdarzenia interfejsu użytkownika do ukończenia, zakleszczysz się nawet w zagnieżdżonym kontekście. W takim przypadku możesz uruchomić asyncmetodę w puli wątków:
var task = Task.Run(async () => await MyAsyncMethod());
var result = task.WaitAndUnwrapException();
To rozwiązanie wymaga jednak MyAsyncMethoddziałania w kontekście puli wątków. Nie może więc aktualizować elementów interfejsu użytkownika ani uzyskiwać dostępu do kontekstu żądań ASP.NET. W takim przypadku możesz również dodać ConfigureAwait(false)do jego awaitoświadczeń i użyć rozwiązania A.
Aktualizacja, 01.05.2019: Obecne „najgorsze praktyki” znajdują się w artykule MSDN tutaj .