Muszę wywołać async
metodę w catch
bloku przed ponownym rzuceniem wyjątku (z jego śladem stosu) w następujący sposób:
try
{
// Do something
}
catch
{
// <- Clean things here with async methods
throw;
}
Ale niestety nie możesz używać await
w a catch
or finally
block. Dowiedziałem się, że to dlatego, że kompilator nie ma sposobu, aby wrócić w catch
bloku, aby wykonać to, co jest po twojej await
instrukcji lub coś w tym rodzaju ...
Próbowałem użyć Task.Wait()
do wymiany await
i dostałem impasu. Szukałem w Internecie, jak mogę tego uniknąć i znalazłem tę witrynę .
Ponieważ nie mogę zmienić async
metod ani nie wiem, czy używają ConfigureAwait(false)
, stworzyłem te metody, które pobierają a, Func<Task>
która uruchamia metodę asynchroniczną, gdy jesteśmy w innym wątku (aby uniknąć impasu) i czeka na jej zakończenie:
public static void AwaitTaskSync(Func<Task> action)
{
Task.Run(async () => await action().ConfigureAwait(false)).Wait();
}
public static TResult AwaitTaskSync<TResult>(Func<Task<TResult>> action)
{
return Task.Run(async () => await action().ConfigureAwait(false)).Result;
}
public static void AwaitSync(Func<IAsyncAction> action)
{
AwaitTaskSync(() => action().AsTask());
}
public static TResult AwaitSync<TResult>(Func<IAsyncOperation<TResult>> action)
{
return AwaitTaskSync(() => action().AsTask());
}
Więc moje pytania brzmi: czy uważasz, że ten kod jest w porządku?
Oczywiście, jeśli masz jakieś ulepszenia lub znasz lepsze podejście, słucham! :)
źródło
await
w bloku catch jest faktycznie dozwolony od C # 6.0 (patrz moją odpowiedź poniżej)Odpowiedzi:
Możesz przenieść logikę poza
catch
blok i ponownie zgłosić wyjątek po, jeśli to konieczne, za pomocąExceptionDispatchInfo
.static async Task f() { ExceptionDispatchInfo capturedException = null; try { await TaskThatFails(); } catch (MyException ex) { capturedException = ExceptionDispatchInfo.Capture(ex); } if (capturedException != null) { await ExceptionHandler(); capturedException.Throw(); } }
W ten sposób, gdy wywołujący sprawdza właściwość wyjątku
StackTrace
, nadal rejestruje, gdzie do środkaTaskThatFails
został rzucony.źródło
ExceptionDispatchInfo
zamiastException
(jak w odpowiedzi Stephena Cleary'ego)?Exception
, stracisz wszystkie poprzednieStackTrace
?Powinieneś wiedzieć, że od C # 6.0 możliwe jest użycie
await
incatch
ifinally
bloki, więc faktycznie możesz to zrobić:try { // Do something } catch (Exception ex) { await DoCleanupAsync(); throw; }
Nowe funkcje C # 6.0, w tym ta, o której wspomniałem, są wymienione tutaj lub jako wideo tutaj .
źródło
Jeśli potrzebujesz obsługi
async
błędów, polecam coś takiego:Exception exception = null; try { ... } catch (Exception ex) { exception = ex; } if (exception != null) { ... }
Problem z synchronicznym blokowaniem
async
kodu (niezależnie od tego, w którym wątku działa) polega na tym, że blokujesz synchronicznie. W większości scenariuszy lepiej jest użyćawait
.Aktualizacja: ponieważ musisz rzucić ponownie, możesz użyć
ExceptionDispatchInfo
.źródło
throw exception;
wif
instrukcji, ślad stosu zostanie utracony.W naszym projekcie wyodrębniliśmy świetną odpowiedź hvd na następującą klasę narzędzi wielokrotnego użytku:
public static class TryWithAwaitInCatch { public static async Task ExecuteAndHandleErrorAsync(Func<Task> actionAsync, Func<Exception, Task<bool>> errorHandlerAsync) { ExceptionDispatchInfo capturedException = null; try { await actionAsync().ConfigureAwait(false); } catch (Exception ex) { capturedException = ExceptionDispatchInfo.Capture(ex); } if (capturedException != null) { bool needsThrow = await errorHandlerAsync(capturedException.SourceException).ConfigureAwait(false); if (needsThrow) { capturedException.Throw(); } } } }
Można by go użyć w następujący sposób:
public async Task OnDoSomething() { await TryWithAwaitInCatch.ExecuteAndHandleErrorAsync( async () => await DoSomethingAsync(), async (ex) => { await ShowMessageAsync("Error: " + ex.Message); return false; } ); }
Nie krępuj się ulepszyć nazewnictwo, celowo pozostawiliśmy je rozwlekłe. Zauważ, że nie ma potrzeby przechwytywania kontekstu wewnątrz opakowania, ponieważ jest on już przechwycony w witrynie wywołania, stąd
ConfigureAwait(false)
.źródło