Czy przy użyciu asynchronicznego protokołu CTP firmy Microsoft dla platformy .NET można wychwycić wyjątek zgłoszony przez metodę asynchroniczną w metodzie wywołującej?
public async void Foo()
{
var x = await DoSomethingAsync();
/* Handle the result, but sometimes an exception might be thrown.
For example, DoSomethingAsync gets data from the network
and the data is invalid... a ProtocolException might be thrown. */
}
public void DoFoo()
{
try
{
Foo();
}
catch (ProtocolException ex)
{
/* The exception will never be caught.
Instead when in debug mode, VS2010 will warn and continue.
The deployed the app will simply crash. */
}
}
Zasadniczo chcę, aby wyjątek od kodu asynchronicznego pojawił się w moim kodzie wywołującym, jeśli jest to w ogóle możliwe.
Odpowiedzi:
Jest to trochę dziwne do przeczytania, ale tak, wyjątek spowoduje wyświetlenie kodu wywołującego - ale tylko wtedy, gdy ty
await
lubWait()
połączenie zFoo
.Zauważ, że użycie Wait () może powodować blokowanie twojej aplikacji, jeśli .Net zdecyduje się wykonać twoją metodę synchronicznie.
To wyjaśnienie http://www.interact-sw.co.uk/iangblog/2010/11/01/csharp5-async-exceptions jest całkiem dobre - omawia kroki kompilatora, aby osiągnąć tę magię.
źródło
await
zadzwonić do Foo, kiedy Foo powróci nieważne?async void Foo()
.Type void is not awaitable
?Powodem, dla którego wyjątek nie został wyłapany, jest to, że metoda Foo () ma typ void return, a więc po wywołaniu funkcji wyczekiwania po prostu zwraca. Ponieważ DoFoo () nie oczekuje na zakończenie Foo, nie można użyć procedury obsługi wyjątków.
Otwiera to prostsze rozwiązanie, czy można zmienić podpisów metoda - zmieniać
Foo()
tak, że zwraca typTask
, a następnieDoFoo()
możnaawait Foo()
, jak w tym kodzie:źródło
Twój kod nie działa tak, jak myślisz. Metody asynchroniczne powracają natychmiast po tym, jak metoda zacznie czekać na wynik asynchroniczny. Wgląd w śledzenie w celu sprawdzenia, jak działa kod, jest wnikliwy.
Poniższy kod wykonuje następujące czynności:
Kiedy obserwujesz ślady
Zauważysz, że metoda Run kończy się na wątku 2820, podczas gdy tylko jeden wątek potomny został zakończony (2756). Jeśli umieścisz metodę try / catch w metodzie oczekującej, możesz „złapać” wyjątek w zwykły sposób, chociaż kod jest wykonywany w innym wątku, gdy zadanie obliczeniowe jest zakończone, a połączenie jest wykonywane.
Metoda obliczania automatycznie śledzi zgłoszony wyjątek, ponieważ użyłem ApiChange.Api.dll z narzędzia ApiChange . Śledzenie i odbłyśnik bardzo pomaga zrozumieć, co się dzieje. Aby pozbyć się wątków, możesz utworzyć własne wersje GetAwaiter BeginAwait i EndAwait i nie zawijać zadania, ale np. Lazy i śledzić własne metody rozszerzenia. Wtedy znacznie lepiej zrozumiesz, co robi kompilator i co robi licencja TPL.
Teraz widzisz, że nie ma sposobu na odzyskanie wyjątku, ponieważ nie ma żadnej ramki stosu dla żadnego wyjątku do propagacji. Twój kod może robić coś zupełnie innego po zainicjowaniu operacji asynchronicznych. Może wywoływać Thread.Sleep lub nawet zakończyć. Tak długo, jak pozostanie jeden wątek pierwszego planu, aplikacja będzie z przyjemnością kontynuować wykonywanie zadań asynchronicznych.
Możesz obsłużyć wyjątek w metodzie asynchronicznej po zakończeniu operacji asynchronicznej i wywołaniu zwrotnym w wątku interfejsu użytkownika. Zalecanym sposobem jest skorzystanie z TaskScheduler.FromSynchronizationContext . Działa to tylko wtedy, gdy masz wątek interfejsu użytkownika i nie jest zbyt zajęty innymi rzeczami.
źródło
Wyjątek można przechwycić w funkcji asynchronicznej.
źródło
Ważne jest również, aby pamiętać, że utracisz chronologiczny ślad stosu wyjątku, jeśli masz typ zwrotu void dla metody asynchronicznej. Polecam zwrócenie zadania w następujący sposób. Sprawi, że debugowanie będzie o wiele łatwiejsze.
źródło
return
instrukcji, ten kod działa jednak, ponieważTask
„niejawnie” jest zwracany za pomocąasync / await
.Ten blog starannie wyjaśnia Twój problem Async Best Practices .
Istotą tego jest to, że nie powinieneś używać void jako zwrotu dla metody asynchronicznej, chyba że jest to asynchroniczna procedura obsługi zdarzeń, jest to zła praktyka, ponieważ nie pozwala na wychwytywanie wyjątków ;-).
Najlepszą praktyką byłoby zmienić typ zwrotu na Zadanie. Spróbuj także kodować asynchronicznie przez całą drogę, wywoływać każde wywołanie metody asynchronicznej i być wywoływanym z metod asynchronicznych. Z wyjątkiem metody Main w konsoli, która nie może być asynchroniczna (przed C # 7.1).
Jeśli zignorujesz tę najlepszą praktykę, wpadniesz w impas w aplikacjach GUI i ASP.NET. Zakleszczenie występuje, ponieważ aplikacje te działają w kontekście, który pozwala tylko na jeden wątek i nie zrzeknie się go w wątku asynchronicznym. Oznacza to, że GUI czeka synchronicznie na zwrot, podczas gdy metoda asynchroniczna czeka na kontekst: zakleszczenie.
To zachowanie nie występuje w aplikacji konsoli, ponieważ działa w kontekście z pulą wątków. Metoda asynchroniczna powróci w innym wątku, który zostanie zaplanowany. Dlatego aplikacja konsoli testowej będzie działać, ale te same połączenia będą działać w impasie w innych aplikacjach ...
źródło