Хорошее решение для ожидания в try/catch/наконец?
Мне нужно вызвать метод 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>
, который запускает метод async, когда мы находимся в другом потоке (чтобы избежать тупик) и ждет его завершения:
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());
}
Итак, мои вопросы: как вы думаете, этот код в порядке?
Конечно, если у вас есть некоторые улучшения или вы знаете лучший подход, я слушаю!:)
Ответы
Ответ 1
Вы можете перемещать логику за пределами блока 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
он был брошен.
Ответ 2
Вы должны знать, что с С# 6.0 можно использовать блоки await
в catch
и finally
, поэтому вы действительно можете это сделать:
try
{
// Do something
}
catch (Exception ex)
{
await DoCleanupAsync();
throw;
}
Новые функции С# 6.0, включая тот, который я только что упомянул перечислены здесь или как видео здесь.
Ответ 3
Если вам нужно использовать обработчики ошибок async
, я бы рекомендовал что-то вроде этого:
Exception exception = null;
try
{
...
}
catch (Exception ex)
{
exception = ex;
}
if (exception != null)
{
...
}
Проблема синхронной блокировки кода async
(независимо от того, какой поток работает) заключается в том, что вы синхронно блокируете. В большинстве сценариев лучше использовать await
.
Обновление:. Поскольку вам нужно реконструировать, вы можете использовать ExceptionDispatchInfo
.
Ответ 4
Мы извлекли 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)
.