Mam public async void Foo()
metodę, którą chcę wywołać z metody synchronicznej. Jak dotąd wszystko, co widziałem w dokumentacji MSDN, to wywoływanie metod asynchronicznych za pomocą metod asynchronicznych, ale cały mój program nie jest zbudowany za pomocą metod asynchronicznych.
Czy to w ogóle możliwe?
Oto jeden przykład wywołania tych metod z metody asynchronicznej: http://msdn.microsoft.com/en-us/library/hh300224(v=vs.110).aspx
Teraz zastanawiam się nad wywołaniem tych metod asynchronicznych z metod synchronizacji.
c#
async-await
Wieża
źródło
źródło
async void Foo()
metoda nie zwraca aTask
oznacza to, że osoba dzwoniąca nie może wiedzieć, kiedy się zakończy, musiTask
zamiast tego powrócić .Odpowiedzi:
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
:Zdajesz nie chcą używać
Task.Wait
alboTask.Result
ponieważ owinąć wyjątkiAggregateException
.To rozwiązanie jest odpowiednie tylko wtedy,
MyAsyncMethod
gdy nie synchronizuje się z kontekstem. Innymi słowy, każdyawait
wMyAsyncMethod
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:* Aktualizacja 14.04.2014: W nowszych wersjach biblioteki interfejs API wygląda następująco:
(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:Task
.Task
.async
Metoda wykorzystujeawait
bezConfigureAwait
.Task
można ukończyć w tej sytuacji, ponieważ kończy się dopiero po zakończeniuasync
metody;async
metoda może nie jest kompletna, ponieważ stara się zaplanować swoją kontynuację doSynchronizationContext
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śliasync
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: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 jegoawait
oświadczeń i użyć rozwiązania A.Aktualizacja, 01.05.2019: Obecne „najgorsze praktyki” znajdują się w artykule MSDN tutaj .
źródło
WaitAndUnwrapException
to moja własna metoda z mojej biblioteki AsyncEx . Oficjalne biblioteki .NET nie zapewniają dużej pomocy w mieszaniu kodu synchronizacji i asynchronicznego (i ogólnie nie powinieneś tego robić!). Czekam na .NET 4.5 RTW i nowego laptopa bez XP, zanim zaktualizuję AsyncEx do działania w wersji 4.5 (nie mogę obecnie programować w wersji 4.5, ponieważ utknąłem na XP przez kilka tygodni).AsyncContext
ma terazRun
metodę, która przyjmuje wyrażenie lambda, więc powinieneś użyćvar result = AsyncContext.Run(() => MyAsyncMethod());
RunTask
metody. Najbliższa rzecz, jaką mogłem znaleźćRun
, to nie maResult
własności.var result = AsyncContext.Run(MyAsyncMethod);
Dodając rozwiązanie, które w końcu rozwiązało mój problem, mam nadzieję, że zaoszczędzi komuś czas.
Najpierw przeczytaj kilka artykułów Stephena Cleary'ego :
Z „dwóch najlepszych praktyk” w „Don't Block on Async Code”, pierwsza nie działała dla mnie, a druga nie miała zastosowania (w zasadzie jeśli mogę
await
, to mogę!).Oto moje obejście: zamknij połączenie w środku
Task.Run<>(async () => await FunctionAsync());
i mam nadzieję, że nie będzie już impasu .Oto mój kod:
źródło
Task.Run()
nie jest to najlepsza praktyka w kodzie asynchronicznym. Ale znowu, jaka jest odpowiedź na pierwotne pytanie? Nigdy nie wywoływać metody asynchronicznej synchronicznie? Chcemy, ale w prawdziwym świecie czasem musimy.Parallel.ForEach
nadużycia nie będą miały wpływu na „rzeczywisty świat” i ostatecznie spowodują uszkodzenie serwerów. Ten kod jest odpowiedni dla aplikacji konsolowych, ale jak mówi @ChrisPratt, nie należy go używać w aplikacjach internetowych. Może działać „teraz”, ale nie jest skalowalny.Microsoft zbudował klasę wewnętrzną AsyncHelper, aby uruchomić Async jako synchronizację. Źródło wygląda następująco:
Klasy podstawowe Microsoft.AspNet.Identity mają tylko metody Async, a do wywołania ich jako Synchronizacja istnieją klasy z metodami rozszerzenia, które wyglądają (przykładowe użycie):
Dla osób zainteresowanych warunkami licencyjnymi kodu, tutaj znajduje się link do bardzo podobnego kodu (dodaje tylko obsługę kultury w wątku), który zawiera komentarze wskazujące, że jest to licencja MIT firmy Microsoft. https://github.com/aspnet/AspNetIdentity/blob/master/src/Microsoft.AspNet.Identity.Core/AsyncHelper.cs
źródło
await
połączeńConfigureAwait(false)
. PróbowałemAsyncHelper.RunSync
wywołać funkcję asynchroniczną zApplication_Start()
funkcji w Global.asax i wydaje się, że działa. Czy to oznacza, żeAsyncHelper.RunSync
niezawodnie nie jest podatny na problem z impasem „marszałka z powrotem w kontekście dzwoniącego”, o którym czytałem w innym miejscu w tym poście?async Main jest teraz częścią C # 7.2 i można go włączyć w zaawansowanych ustawieniach kompilacji projektu.
Dla C # <7.2 poprawny sposób to:
Zostanie to wykorzystane w wielu dokumentacjach Microsoft, na przykład: https://docs.microsoft.com/en-us/azure/service-bus-messaging/service-bus-dotnet-how-to-use- subskrypcje tematów
źródło
MainAsync().Wait()
?Odczytujesz słowo kluczowe „czekaj” jako „rozpocznij to długo działające zadanie, a następnie przywróć kontrolę nad metodą wywoływania”. Po zakończeniu długotrwałego zadania wykonuje on kod po nim. Kod po oczekiwaniu jest podobny do tego, co kiedyś było metodami CallBack. Duża różnica polega na tym, że przepływ logiczny nie jest przerywany, co znacznie ułatwia pisanie i czytanie.
źródło
Wait
omija wyjątki i ma możliwość impasu.await
, zostanie ona wykonana synchronicznie. Przynajmniej to działa dla mnie (bez dzwonieniamyTask.Wait
). Właściwie dostałem wyjątek, gdy próbowałem zadzwonić,myTask.RunSynchronously()
ponieważ został już wykonany!.Result
.Result
wywołanie, więc nigdy go nie dociera. IResult
nigdy się nie kończy, ponieważ czeka na kogoś, kto czeka naResult
koniec, w zasadzie: DNie jestem w 100% pewien, ale uważam, że technika opisana na tym blogu powinna działać w wielu okolicznościach:
źródło
Istnieje jednak dobre rozwiązanie, które działa w (prawie: patrz komentarze) w każdej sytuacji: pompa komunikatów ad-hoc (SynchronizationContext).
Wątek wywołujący zostanie zablokowany zgodnie z oczekiwaniami, jednocześnie zapewniając, że wszystkie kontynuacje wywoływane z funkcji asynchronicznej nie zostaną zakleszczone, ponieważ zostaną skierowane do ad-hoc SynchronizationContext (pompy komunikatów) działającej w wątku wywołującym.
Kod pomocnika pompy komunikatów ad-hoc:
Stosowanie:
Bardziej szczegółowy opis pompy asynchronicznej znajduje się tutaj .
źródło
Do każdego, kto zwraca uwagę na to pytanie ...
Jeśli zajrzysz
Microsoft.VisualStudio.Services.WebApi
do środka, istnieje klasa o nazwieTaskExtensions
. W tej klasie zobaczysz metodę statycznego rozszerzeniaTask.SyncResult()
, która jak całkowicie blokuje wątek, dopóki zadanie nie powróci.Wewnętrznie wywołuje
task.GetAwaiter().GetResult()
to dość proste, ale przeciążone jest pracą nad każdąasync
metodą, która zwracaTask
,Task<T>
lubTask<HttpResponseMessage>
... cukrem syntaktycznym, kochanie ... tata ma słodycze.Wygląda na
...GetAwaiter().GetResult()
to, że jest to oficjalny sposób MS na wykonanie kodu asynchronicznego w kontekście blokującym. Wydaje się, że działa bardzo dobrze w moim przypadku użycia.źródło
Lub użyj tego:
źródło
Możesz wywołać dowolną metodę asynchroniczną z kodu synchronicznego, to znaczy, dopóki nie będziesz musiał
await
na nich użyć, w takim przypadku muszą one również zostać oznaczoneasync
.Jak sugeruje tutaj wiele osób, możesz wywołać funkcję Wait () lub Wynik w wynikowym zadaniu w metodzie synchronicznej, ale w rezultacie w tej metodzie kończy się wywołanie blokujące, które w pewnym sensie nie spełnia celu asynchronizacji.
Naprawdę nie możesz stworzyć metody
async
i nie chcesz blokować metody synchronicznej, wtedy będziesz musiał użyć metody wywołania zwrotnego, przekazując ją jako parametr do metody ContinueWith przy zadaniu.źródło
async
” odwróciły moją uwagę od tego, co naprawdę mówiłeś.Wiem, że jestem spóźniony. Ale na wypadek, gdyby ktoś taki jak ja chciał rozwiązać to w prosty, schludny sposób i bez polegania na innej bibliotece.
Znalazłem następujący fragment kodu od Ryana
możesz to tak nazwać
źródło
Po godzinach próbowania różnych metod, z mniejszym lub większym sukcesem, na tym się skończyłem. Nie kończy się impasem podczas uzyskiwania wyniku, a także pobiera i generuje wyjątek oryginalny, a nie zapakowany.
źródło
Można go wywołać z nowego wątku (NIE z puli wątków!):
źródło
Te metody asynchroniczne systemu Windows mają sprytną małą metodę o nazwie AsTask (). Możesz użyć tego, aby metoda zwróciła się jako zadanie, dzięki czemu możesz ręcznie wywołać na niej Wait ().
Na przykład w aplikacji Silverlight systemu Windows Phone 8 możesz wykonać następujące czynności:
Mam nadzieję że to pomoże!
źródło
Jeśli chcesz go uruchomić, zsynchronizuj
źródło
RunSynchronously()
gorącego zadania doInvalidOperationException
. Wypróbuj z tym kodem:Task.Run(() => {}).RunSynchronously();
źródło