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.Wait
albo Task.Result
ponieważ owinąć wyjątki AggregateException
.
To rozwiązanie jest odpowiednie tylko wtedy, MyAsyncMethod
gdy nie synchronizuje się z kontekstem. Innymi słowy, każdy await
w MyAsyncMethod
powinny 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 MyAsyncMethod
konieczne jest zsynchronizowanie z powrotem do kontekstu, możesz użyć opcji, AsyncContext.RunTask
aby 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.Result
tym przykładzie można używać, ponieważ RunTask
będą propagować Task
wyjątki).
AsyncContext.RunTask
Zamiast tego możesz potrzebować Task.WaitAndUnwrapException
raczej 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
.
async
Metoda wykorzystuje await
bez ConfigureAwait
.
- Nie
Task
można ukończyć w tej sytuacji, ponieważ kończy się dopiero po zakończeniu async
metody; async
metoda może nie jest kompletna, ponieważ stara się zaplanować swoją kontynuację do SynchronizationContext
i 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ą async
metodę w jak największym stopniu.
Rozwiązanie C
AsyncContext.RunTask
nie będzie działać w każdym scenariuszu. Na przykład, jeśli async
metoda 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ć async
metodę w puli wątków:
var task = Task.Run(async () => await MyAsyncMethod());
var result = task.WaitAndUnwrapException();
To rozwiązanie wymaga jednak MyAsyncMethod
dział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 await
oświadczeń i użyć rozwiązania A.
Aktualizacja, 01.05.2019: Obecne „najgorsze praktyki” znajdują się w artykule MSDN tutaj .