95

在再次抛出异常(及其堆栈跟踪)之前,我需要async在块中调用一个方法,如下所示:catch

try
{
    // Do something
}
catch
{
    // <- Clean things here with async methods
    throw;
}

但不幸的是,您不能await在 acatchfinally块中使用。我了解到这是因为编译器没有任何方法可以返回一个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());
}

所以我的问题是:你认为这段代码可以吗?

当然,如果您有一些改进或知道更好的方法,我在听!:)

4

4 回答 4

177

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.

于 2013-05-18T16:07:03.843 回答
55

您应该知道,从 C# 6.0 开始,可以使用awaitincatchfinally块,因此您实际上可以这样做:

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

新的 C# 6.0 特性,包括我刚才提到的特性,都在此处列出或作为视频列出。

于 2014-11-15T17:05:24.083 回答
16

如果您需要使用async错误处理程序,我建议您这样做:

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

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

在代码上同步阻塞的问题async(不管它在哪个线程上运行)是你在同步阻塞。在大多数情况下,最好使用await.

更新:由于您需要重新抛出,您可以使用ExceptionDispatchInfo.

于 2013-05-18T16:03:28.143 回答
3

我们在我们的项目中提取了 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).

于 2014-11-11T14:35:41.733 回答