A good solution for await in try/catch/finally?

情到浓时终转凉″ 提交于 2019-11-27 17:00:56

You can move the logic outside of the catch block and rethrow the exception after, if needed, by using 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();
    }
}

This way, when the caller inspects the exception's StackTrace property, it still records where inside TaskThatFails it was thrown.

You should know that since C# 6.0, it's possible to use await in catch and finally blocks, so you could in fact do this:

try
{
    // Do something
}
catch (Exception ex)
{
    await DoCleanupAsync();
    throw;
}

The new C# 6.0 features, including the one I just mentioned are listed here or as a video here.

If you need to use async error handlers, I'd recommend something like this:

Exception exception = null;
try
{
  ...
}
catch (Exception ex)
{
  exception = ex;
}

if (exception != null)
{
  ...
}

The problem with synchronously blocking on async code (regardless of what thread it's running on) is that you're synchronously blocking. In most scenarios, it's better to use await.

Update: Since you need to rethrow, you can use ExceptionDispatchInfo.

mrts

We extracted hvd's great answer to the following reusable utility class in our project:

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();
            }
        }
    }
}

One would use it as follows:

    public async Task OnDoSomething()
    {
        await TryWithAwaitInCatch.ExecuteAndHandleErrorAsync(
            async () => await DoSomethingAsync(),
            async (ex) => { await ShowMessageAsync("Error: " + ex.Message); return false; }
        );
    }

Feel free to improve the naming, we kept it intentionally verbose. Note that there is no need to capture the context inside the wrapper as it is already captured in the call site, hence ConfigureAwait(false).

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!