Uczę się o asynchronizacji / czekaniu i wpadłem na sytuację, w której muszę synchronicznie wywołać metodę asynchroniczną. Jak mogę to zrobić?
Metoda asynchroniczna:
public async Task<Customers> GetCustomers()
{
return await Service.GetCustomersAsync();
}
Normalne użycie:
public async void GetCustomers()
{
customerList = await GetCustomers();
}
Próbowałem użyć następujących opcji:
Task<Customer> task = GetCustomers();
task.Wait()
Task<Customer> task = GetCustomers();
task.RunSynchronously();
Task<Customer> task = GetCustomers();
while(task.Status != TaskStatus.RanToCompletion)
Próbowałem też stąd sugestii , jednak nie działa ona, gdy dyspozytor jest w stanie zawieszenia.
public static void WaitWithPumping(this Task task)
{
if (task == null) throw new ArgumentNullException(“task”);
var nestedFrame = new DispatcherFrame();
task.ContinueWith(_ => nestedFrame.Continue = false);
Dispatcher.PushFrame(nestedFrame);
task.Wait();
}
Oto wyjątek i ślad stosu z wywołania RunSynchronously
:
System.InvalidOperationException
Wiadomość : RunSynchronously nie można wywołać w przypadku zadania niezwiązanego z delegatem.
InnerException : null
Źródło : mscorlib
StackTrace :
at System.Threading.Tasks.Task.InternalRunSynchronously(TaskScheduler scheduler)
at System.Threading.Tasks.Task.RunSynchronously()
at MyApplication.CustomControls.Controls.MyCustomControl.CreateAvailablePanelList() in C:\Documents and Settings\...\MyApplication.CustomControls\Controls\MyCustomControl.xaml.cs:line 638
at MyApplication.CustomControls.Controls.MyCustomControl.get_AvailablePanels() in C:\Documents and Settings\...\MyApplication.CustomControls\Controls\MyCustomControl.xaml.cs:line 233
at MyApplication.CustomControls.Controls.MyCustomControl.<CreateOpenPanelList>b__36(DesktopPanel panel) in C:\Documents and Settings\...\MyApplication.CustomControls\Controls\MyCustomControl.xaml.cs:line 597
at System.Collections.Generic.List`1.ForEach(Action`1 action)
at MyApplication.CustomControls.Controls.MyCustomControl.<CreateOpenPanelList>d__3b.MoveNext() in C:\Documents and Settings\...\MyApplication.CustomControls\Controls\MyCustomControl.xaml.cs:line 625
at System.Runtime.CompilerServices.TaskAwaiter.<>c__DisplayClass7.<TrySetContinuationForAwait>b__1(Object state)
at System.Windows.Threading.ExceptionWrapper.InternalRealCall(Delegate callback, Object args, Int32 numArgs)
at MS.Internal.Threading.ExceptionFilterHelper.TryCatchWhen(Object source, Delegate method, Object args, Int32 numArgs, Delegate catchHandler)
at System.Windows.Threading.DispatcherOperation.InvokeImpl()
at System.Windows.Threading.DispatcherOperation.InvokeInSecurityContext(Object state)
at System.Threading.ExecutionContext.runTryCode(Object userData)
at System.Runtime.CompilerServices.RuntimeHelpers.ExecuteCodeWithGuaranteedCleanup(TryCode code, CleanupCode backoutCode, Object userData)
at System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state)
at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean ignoreSyncCtx)
at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state)
at System.Windows.Threading.DispatcherOperation.Invoke()
at System.Windows.Threading.Dispatcher.ProcessQueue()
at System.Windows.Threading.Dispatcher.WndProcHook(IntPtr hwnd, Int32 msg, IntPtr wParam, IntPtr lParam, Boolean& handled)
at MS.Win32.HwndWrapper.WndProc(IntPtr hwnd, Int32 msg, IntPtr wParam, IntPtr lParam, Boolean& handled)
at MS.Win32.HwndSubclass.DispatcherCallbackOperation(Object o)
at System.Windows.Threading.ExceptionWrapper.InternalRealCall(Delegate callback, Object args, Int32 numArgs)
at MS.Internal.Threading.ExceptionFilterHelper.TryCatchWhen(Object source, Delegate method, Object args, Int32 numArgs, Delegate catchHandler)
at System.Windows.Threading.Dispatcher.InvokeImpl(DispatcherPriority priority, TimeSpan timeout, Delegate method, Object args, Int32 numArgs)
at MS.Win32.HwndSubclass.SubclassWndProc(IntPtr hwnd, Int32 msg, IntPtr wParam, IntPtr lParam)
at MS.Win32.UnsafeNativeMethods.DispatchMessage(MSG& msg)
at System.Windows.Threading.Dispatcher.PushFrameImpl(DispatcherFrame frame)
at System.Windows.Threading.Dispatcher.PushFrame(DispatcherFrame frame)
at System.Windows.Threading.Dispatcher.Run()
at System.Windows.Application.RunDispatcher(Object ignore)
at System.Windows.Application.RunInternal(Window window)
at System.Windows.Application.Run(Window window)
at System.Windows.Application.Run()
at MyApplication.App.Main() in C:\Documents and Settings\...\MyApplication\obj\Debug\App.g.cs:line 50
at System.AppDomain._nExecuteAssembly(RuntimeAssembly assembly, String[] args)
at System.AppDomain.ExecuteAssembly(String assemblyFile, Evidence assemblySecurity, String[] args)
at Microsoft.VisualStudio.HostingProcess.HostProc.RunUsersAssembly()
at System.Threading.ThreadHelper.ThreadStart_Context(Object state)
at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean ignoreSyncCtx)
at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state)
at System.Threading.ThreadHelper.ThreadStart()
c#
asynchronous
c#-5.0
async-await
Rachel
źródło
źródło
Task
synchroniczne; 3) Integruję GeneXus ze sterownikiem MongoDB C # , którySemaphoreSlim
.Odpowiedzi:
Oto obejście, które według mnie działa we wszystkich przypadkach (w tym w zawieszonych dyspozytorach). To nie jest mój kod i wciąż staram się go w pełni zrozumieć, ale działa.
Można go nazwać za pomocą:
customerList = AsyncHelpers.RunSync<List<Customer>>(() => GetCustomers());
Kod jest stąd
źródło
DynamicNodeProviderBase
klasy podstawowej, nie można zadeklarować jej jakoasync
metody. Albo musiałem zastąpić nową biblioteką, albo po prostu wywołać synchroniczną operację.AspNetSynchronizationContext
, więc ten konkretny hack nie zadziała, jeśli wywołujesz te interfejsy API.Informujemy, że ta odpowiedź ma trzy lata. Napisałem go w oparciu o doświadczenie z .Net 4.0, a bardzo mało z 4.5 szczególnie z
async-await
. Ogólnie rzecz biorąc, jest to ładne proste rozwiązanie, ale czasami psuje rzeczy. Przeczytaj dyskusję w komentarzach..Net 4.5
Po prostu użyj tego:
Zobacz: TaskAwaiter , Task.Result , Task.RunSynchronously
.Net 4.0
Użyj tego:
...albo to:
źródło
.Result
może wywołać impas w niektórych scenariuszachResult
mogę łatwo spowodować zakleszczenie wasync
kodzie , jak opisuję na moim blogu.Zaskoczony nikt nie wspomniał o tym:
Nie tak ładny, jak niektóre inne metody tutaj, ale ma następujące zalety:
Wait
)AggregateException
(jakResult
)Task
iTask<T>
( wypróbuj to sam! )Ponadto, ponieważ
GetAwaiter
jest typu kaczego, powinno to działać dla każdego obiektu zwracanego z metody asynchronicznej (jakConfiguredAwaitable
lubYieldAwaitable
), nie tylko Zadania.edycja: Należy pamiętać, że to podejście (lub użycie
.Result
) jest możliwe do zakleszczenia, chyba że pamiętasz o dodawaniu za.ConfigureAwait(false)
każdym razem, gdy czekasz, dla wszystkich metod asynchronicznych, z których można uzyskać dostępBlahAsync()
(nie tylko te, które wywołuje bezpośrednio). Wyjaśnienie .Jeśli jesteś zbyt leniwy, aby dodawać
.ConfigureAwait(false)
wszędzie i nie zależy ci na wydajności, możesz to zrobić alternatywnieźródło
GetAwaiter()
: „Ta metoda jest przeznaczona dla użytkownika kompilatora, a nie do bezpośredniego użycia w kodzie”.O wiele łatwiej jest uruchomić zadanie w puli wątków, niż próbować nakłonić program planujący do uruchomienia go synchronicznie. W ten sposób możesz mieć pewność, że nie zablokuje się. Wpływ na wydajność ma zmiana kontekstu.
źródło
Task.Run(DoSomethingAsync)
zamiast tego? To usuwa jeden poziom delegatów.Task<MyResult> task = Task.Run(async () => await DoSomethingAsync());
pójście w przeciwnym kierunku, jak w, jest bardziej wyraźne i odnosi się do obawy @sgnsajgon, że może zwracać Zadanie <Zadanie <Mój Rezultat>>. Prawidłowe przeciążenie Task.Run jest wybierane w obu kierunkach, ale delegat asynchroniczny czyni twój zamiar oczywistym.Najlepszą odpowiedzią jest to, że nie , a szczegóły zależą od tego, jaka jest „sytuacja”.
Czy jest to getter / setter? W większości przypadków lepiej jest mieć metody asynchroniczne niż „właściwości asynchroniczne”. (Aby uzyskać więcej informacji, zobacz mój post na blogu o właściwościach asynchronicznych ).
Czy to aplikacja MVVM i chcesz wykonać asynchroniczne wiązanie danych? Następnie użyj czegoś takiego jak mój
NotifyTask
, jak opisano w moim artykule MSDN na temat asynchronicznego wiązania danych .Czy to konstruktor? Prawdopodobnie chcesz rozważyć asynchroniczną metodę fabryczną. (Aby uzyskać więcej informacji, zobacz mój post na blogu o konstruktorach asynchronicznych ).
Prawie zawsze istnieje lepsza odpowiedź niż synchronizacja przez asynchronizację.
Jeśli nie jest to możliwe w Twojej sytuacji (i wiesz o tym, zadając pytanie opisujące sytuację ), zalecam użycie kodu synchronicznego. Asynchronizacja jest najlepsza; synchronizacja do końca jest najlepsza. Synchronizacja nad asynchronią nie jest zalecana.
Istnieje jednak kilka sytuacji, w których konieczna jest synchronizacja przez asynchronizację. Konkretnie, jesteś ograniczony przez kodu wywołującego, dzięki czemu mają być synchronizację (i nie mają żadnego drogę do ponownego przemyślenia lub re-konstrukcja kod, aby umożliwić asynchrony), a ty masz zadzwonić asynchroniczny kodu. Jest to bardzo rzadka sytuacja, ale pojawia się od czasu do czasu.
W takim przypadku musisz użyć jednego z hacków opisanych w moim artykule na temat rozwoju terenów poprzemysłowych
async
, a konkretnie:GetAwaiter().GetResult()
.). Pamiętaj, że może to powodować impasy (jak opisuję na moim blogu).Task.Run(..).GetAwaiter().GetResult()
.). Zauważ, że zadziała to tylko wtedy, gdy kod asynchroniczny można uruchomić w wątku puli wątków (tzn. Nie jest zależny od interfejsu użytkownika lub kontekstu ASP.NET).Zagnieżdżone pętle wiadomości są najniebezpieczniejszym ze wszystkich włamań, ponieważ powodują ponowną rozrywkę . Ponowne wejście jest bardzo trudne do uzasadnienia, a (IMO) jest przyczyną większości błędów aplikacji w systemie Windows. W szczególności, jeśli korzystasz z wątku interfejsu użytkownika i blokujesz w kolejce roboczej (czekając na zakończenie pracy asynchronicznej), CLR faktycznie wykonuje pompowanie wiadomości za Ciebie - w rzeczywistości obsłuży niektóre komunikaty Win32 z twojego kod . Aha, i nie masz pojęcia, które wiadomości - kiedy Chris Brumme mówi: „Czy nie byłoby wspaniale wiedzieć, co zostanie napompowane? Niestety, pompowanie jest czarną sztuką, która jest poza śmiertelnym zrozumieniem”., wtedy naprawdę nie mamy nadziei na to, aby wiedzieć.
Tak więc, gdy blokujesz w ten sposób w wątku interfejsu użytkownika, pytasz o problemy. Kolejny cytat z tego samego artykułu: „Od czasu do czasu klienci w firmie lub poza nią odkrywają, że pompujemy wiadomości podczas zarządzanego blokowania STA [wątku interfejsu użytkownika]. Jest to uzasadniony problem, ponieważ wiedzą, że jest to bardzo trudne do pisania solidnego kodu w obliczu ponownego pojawienia się ”.
Tak to jest. Bardzo trudno pisać kod, który jest solidny w obliczu ponownego pojawienia się. Zagnieżdżone pętle wiadomości zmuszają do pisania kodu, który jest solidny w obliczu ponownego pojawienia się. Dlatego przyjęta (i najbardziej pozytywnie oceniona) odpowiedź na to pytanie jest w praktyce bardzo niebezpieczna .
Jeśli całkowicie nie masz żadnych innych opcji - nie możesz przeprojektować kodu, nie możesz go zrestrukturyzować, aby był asynchroniczny - jesteś zmuszony przez niezmienny kod wywołujący do synchronizacji - nie możesz zmienić kodu dalszego do synchronizacji - nie możesz zablokować - nie możesz uruchomić kodu asynchronicznego w osobnym wątku - wtedy i tylko wtedy powinieneś rozważyć przyjęcie ponownego ustalenia.
Jeśli znajdziesz się w tym kącie, polecam użycie czegoś takiego jak
Dispatcher.PushFrame
do aplikacji WPF , zapętlanie zApplication.DoEvents
aplikacjami WinForm i, ogólnie, mój własnyAsyncContext.Run
.źródło
Main()
metodą asynchroniczną nie kompiluje się; w pewnym momencie już dostał się do wypełnienia luki między synchronizacji i async światów. To nie jest „ bardzo rzadka sytuacja” , jest niezbędna dosłownie w każdym programie, który wywołuje metodę asynchroniczną. Nie ma opcji, aby „ nie wykonywać synchronizacji przez asynchronizację” , tylko opcja przerzucenia obciążenia na metodę wywoływania zamiast narzucania jej w tej, którą właśnie piszesz.async
wszystkie metody w mojej aplikacji. I to dużo. Czy to nie może być domyślne?Jeśli dobrze czytam twoje pytanie - kod, który chce synchronicznego wywołania metody asynchronicznej, jest wykonywany w zawieszonym wątku programu rozsyłającego. I chcesz faktycznie synchronicznie zablokować ten wątek, dopóki metoda asynchroniczna nie zostanie zakończona.
Metody asynchroniczne w C # 5 są oparte na skutecznym pocięciu metody na kawałki pod maską i zwróceniu metody,
Task
która może śledzić ogólne zakończenie całego shabang. Jednak sposób wykonania podzielonych metod może zależeć od typu wyrażenia przekazanego doawait
operatorowi.Przez większość czasu będziesz używać
await
wyrażenia typuTask
. Implementacjaawait
wzorca zadania jest „inteligentna”, ponieważ odsyła doSynchronizationContext
, co w zasadzie powoduje, że zdarzają się:await
jest w wątku pętli komunikatów programu Dispatcher lub WinForms, zapewnia to, że fragmenty metody asynchronicznej występują w ramach przetwarzania kolejki komunikatów.await
znajduje się w wątku puli wątków, pozostałe fragmenty metody asynchronicznej występują w dowolnym miejscu w puli wątków.Dlatego prawdopodobnie masz problemy - implementacja metody asynchronicznej próbuje uruchomić resztę w programie Dispatcher - nawet jeśli jest zawieszona.
... tworzenie kopii zapasowej! …
Muszę zadać pytanie, dlaczego próbujesz synchronicznie blokować metodę asynchroniczną? Takie postępowanie zniweczyłoby cel, dla którego metoda chciała być wywoływana asynchronicznie. Ogólnie rzecz biorąc, kiedy zaczniesz używać
await
metody Dispatcher lub interfejsu użytkownika, będziesz chciał zmienić cały asynchroniczny przepływ interfejsu użytkownika. Na przykład, jeśli twój callstack był podobny do następującego:WebRequest.GetResponse()
YourCode.HelperMethod()
YourCode.AnotherMethod()
YourCode.EventHandlerMethod()
[UI Code].Plumbing()
-WPF
lubWinForms
KodWPF
lubWinForms
Message LoopNastępnie, gdy kod zostanie przekształcony w celu użycia asynchronizacji, zwykle kończy się to
WebRequest.GetResponseAsync()
YourCode.HelperMethodAsync()
YourCode.AnotherMethodAsync()
YourCode.EventHandlerMethodAsync()
[UI Code].Plumbing()
-WPF
lubWinForms
KodWPF
lubWinForms
Message LoopWłaściwie odpowiadanie
Powyższa klasa AsyncHelpers faktycznie działa, ponieważ zachowuje się jak zagnieżdżona pętla komunikatów, ale instaluje własną funkcję równoległą w programie Dispatcher, zamiast próbować wykonać go w samym programie Dispatcher. To jedno obejście problemu.
Innym obejściem jest wykonanie metody asynchronicznej w wątku puli wątków, a następnie poczekanie na jej zakończenie. Jest to łatwe - możesz to zrobić za pomocą następującego fragmentu kodu:
Ostatecznym interfejsem API będzie Task.Run (...), ale przy CTP potrzebne będą sufiksy Ex ( wyjaśnienie tutaj ).
źródło
TaskEx.RunEx(GetCustomers).Result
zawiesza aplikację, gdy zostanie uruchomiona w zawieszonym wątku dyspozytora. Ponadto metoda GetCustomers () jest zwykle uruchamiana asynchronicznie, jednak w jednej sytuacji musi działać synchronicznie, więc szukałem sposobu, aby to zrobić bez budowania wersji synchronicznej metody.async
metod; z pewnością należy unikać zagnieżdżonych pętli.To działa dobrze dla mnie
źródło
MyAsyncMethod().RunTaskSynchronously();
Najprostszym sposobem, jaki znalazłem, aby uruchamiać zadanie synchronicznie i bez blokowania wątku interfejsu użytkownika jest użycie RunSynchronously () w następujący sposób:
W moim przypadku zdarzenie jest uruchamiane, gdy coś się wydarzy. Nie wiem, ile razy to nastąpi. Używam powyższego kodu w moim zdarzeniu, więc za każdym razem, gdy się uruchamia, tworzy zadanie. Zadania są wykonywane synchronicznie i działa świetnie dla mnie. Byłem po prostu zaskoczony, że tak długo zajęło mi odkrycie tego, biorąc pod uwagę, jak to jest proste. Zazwyczaj zalecenia są znacznie bardziej złożone i podatne na błędy. To było proste i czyste.
źródło
Stawiłem temu czoła kilka razy, głównie podczas testów jednostkowych lub w rozwoju usług Windows. Obecnie zawsze korzystam z tej funkcji:
To proste, łatwe i nie miałem problemów.
źródło
Znalazłem ten kod w Microsoft.AspNet.Identity.Core i działa.
źródło
Mała uwaga - takie podejście:
działa dla WinRT.
Pozwól mi wyjaśnić:
Co więcej, to podejście działa tylko w przypadku rozwiązań Windows Store!
Uwaga: Ten sposób nie jest bezpieczny dla wątków, jeśli wywołasz metodę w innej metodzie asynchronicznej (zgodnie z komentarzami @Servy)
źródło
CancellationToken
moje rozwiązanie.W twoim kodzie pierwsze oczekiwanie na wykonanie zadania, ale jeszcze go nie uruchomiłeś, więc czeka ono w nieskończoność. Spróbuj tego:
Edytować:
Mówisz, że dostajesz wyjątek. Proszę zamieścić więcej szczegółów, w tym ślad stosu.
Mono zawiera następujący przypadek testowy:
Sprawdź, czy to Ci odpowiada. Jeśli tak się nie stanie, choć bardzo mało prawdopodobne, możesz mieć dziwną kompilację Async CTP. Jeśli to działa, możesz sprawdzić, co dokładnie generuje kompilator i czym
Task
różni się tworzenie instancji od tej próbki.Edytuj # 2:
Sprawdziłem w Reflector, czy opisany wyjątek występuje, gdy
m_action
jestnull
. To trochę dziwne, ale nie jestem ekspertem od Async CTP. Jak powiedziałem, powinieneś zdekompilować swój kod i zobaczyć, jak dokładnieTask
tworzona jest instancja, niezależnie od tego, jakm_action
jestnull
.PS O co chodzi z okazjonalnymi głosowaniami? Możesz rozwinąć temat?
źródło
RunSynchronously may not be called on a task unbound to a delegate
. Google nie pomaga, ponieważ wszystkie wyniki są po chińsku ...await
używane jest słowo kluczowe. Wyjątkiem opublikowanym w moim wcześniejszym komentarzu jest wyjątek, który otrzymuję, chociaż jest to jeden z niewielu, których nie mogę znaleźć w Google i znaleźć przyczynę lub rozwiązanie.async
aasync
słowa kluczowe są niczym więcej niż cukrem składniowym. Kompilator generuje kod, aby utworzyćTask<Customer>
wGetCustomers()
tak to gdzie będę szukać w pierwszej kolejności. Jeśli chodzi o wyjątek, wysłałeś tylko komunikat wyjątku, który jest bezużyteczny bez typu wyjątku i śledzenia stosu. WywołajToString()
metodę wyjątku i wyślij wynik w pytaniu.Testowane w .Net 4.6. Może także uniknąć impasu.
Do zwrotu metody asynchronicznej
Task
.Do zwrotu metody asynchronicznej
Task<T>
Edytuj :
Jeśli program wywołujący działa w wątku puli wątków (lub program wywołujący również wykonuje zadanie), może nadal powodować impas w niektórych sytuacjach.
źródło
Result
jest idealny do pracy, jeśli chcesz synchronicznego połączenia, a wręcz niebezpieczny w przeciwnym razie. W nazwieResult
ani w jej inteligencji nic nieResult
wskazuje na to, że jest to połączenie blokujące. Naprawdę należy go zmienić.użyj poniżej fragmentu kodu
źródło
Dlaczego nie utworzyć połączenia takiego jak:
to nie jest asynchroniczne.
źródło
Ta odpowiedź jest przeznaczona dla każdego, kto używa WPF dla .NET 4.5.
Jeśli spróbujesz wykonać
Task.Run()
w wątku GUI,task.Wait()
zawiesi się na czas nieokreślony, jeśli nie maszasync
słowa kluczowego w definicji funkcji.Ta metoda rozszerzenia rozwiązuje problem, sprawdzając, czy jesteśmy w wątku GUI, a jeśli tak, uruchamiając zadanie w wątku programu rozsyłającego WPF.
Ta klasa może działać jak klej między światem asynchronicznym / oczekującym a światem nie asynchronicznym / oczekującym w sytuacjach, gdy jest to nieuniknione, takich jak właściwości MVVM lub zależności od innych interfejsów API, które nie używają asynchronicznego / oczekującego.
źródło
Wystarczy zadzwonić
.Result;
lub.Wait()
jest to ryzyko impasu, jak wielu powiedziało w komentarzach. Ponieważ większość z nas lubi onelinerów, możesz z nich korzystać.Net 4.5<
Pozyskiwanie wartości metodą asynchroniczną:
Synchroniczne wywoływanie metody asynchronicznej
Z powodu użycia nie wystąpią problemy z zakleszczeniem
Task.Run
.Źródło:
https://stackoverflow.com/a/32429753/3850405
źródło
Myślę, że następująca metoda pomocnicza może również rozwiązać problem.
Można go użyć w następujący sposób:
źródło
To działa dla mnie
źródło
Przekonałem się, że SpinWait działa w tym przypadku całkiem dobrze.
Powyższe podejście nie wymaga użycia .Result lub .Wait (). Pozwala także określić limit czasu, aby nie utknąć na zawsze, na wypadek gdyby zadanie nigdy się nie zakończyło.
źródło
Na wp8:
Zawiń to:
Nazwać:
źródło
źródło
Lub możesz po prostu iść z:
Aby to skompilować, upewnij się, że odwołujesz się do zestawu rozszerzeń:
źródło
Wypróbuj następujący kod, który działa dla mnie:
źródło