Мне нужно вызвать async
метод в catch
блоке, прежде чем снова выбросить исключение (с его трассировкой стека) следующим образом:
try
{
// Do something
}
catch
{
// <- Clean things here with async methods
throw;
}
Но, к сожалению, вы не можете использовать await
в блоке catch
или finally
. Я узнал это, потому что у компилятора нет возможности вернуться в catch
блок, чтобы выполнить то, что было после вашей await
инструкции или что-то в этом роде ...
Пробовал использовать Task.Wait()
для замены await
и у меня тупик. Я искал в Интернете, как мне этого избежать, и нашел этот сайт .
Поскольку я не могу изменить async
методы и не знаю, используются ли они ConfigureAwait(false)
, я создал эти методы, которые принимают Func<Task>
метод, который запускает асинхронный метод, когда мы находимся в другом потоке (чтобы избежать тупиковой ситуации), и ожидает его завершения:
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());
}
Итак, мои вопросы: как вы думаете, этот код в порядке?
Конечно, если у вас есть улучшения или вы знаете лучший подход, я слушаю! :)
источник
await
в блоке catch фактически разрешено с C # 6.0 (см. Мой ответ ниже)Ответы:
Вы можете переместить логику за пределы
catch
блока и при необходимости повторно вызвать исключение, используя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(); } }
Таким образом, когда вызывающий
StackTrace
объект проверяет свойство исключения , он по-прежнему записывает, гдеTaskThatFails
именно оно было выброшено.источник
ExceptionDispatchInfo
вместоException
(как в ответе Стивена Клири)?Exception
, вы потеряете все предыдущееStackTrace
?Вы должны знать , что с C # 6.0, можно использовать
await
вcatch
иfinally
блоках, так что вы на самом деле можете сделать это:try { // Do something } catch (Exception ex) { await DoCleanupAsync(); throw; }
Новые возможности C # 6.0, включая ту, которую я только что упомянул , перечислены здесь или в виде видео здесь .
источник
Если вам нужно использовать
async
обработчики ошибок, я бы порекомендовал что-то вроде этого:Exception exception = null; try { ... } catch (Exception ex) { exception = ex; } if (exception != null) { ... }
Проблема с синхронной блокировкой
async
кода (независимо от того, в каком потоке он выполняется) заключается в том, что вы синхронно блокируете. В большинстве случаев лучше использоватьawait
.Обновление: поскольку вам нужно перебросить, вы можете использовать
ExceptionDispatchInfo
.источник
throw exception;
вif
операторе, трассировка стека будет потеряна.Мы извлекли отличный ответ hvd на следующий многоразовый служебный класс в нашем проекте:
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(); } } } }
Можно было бы использовать его следующим образом:
public async Task OnDoSomething() { await TryWithAwaitInCatch.ExecuteAndHandleErrorAsync( async () => await DoSomethingAsync(), async (ex) => { await ShowMessageAsync("Error: " + ex.Message); return false; } ); }
Не стесняйтесь улучшать именование, мы намеренно сделали его подробным. Обратите внимание, что нет необходимости захватывать контекст внутри оболочки, поскольку он уже захвачен на сайте вызова
ConfigureAwait(false)
.источник