【问题标题】:A good solution for await in try/catch/finally?在 try/catch/finally 中等待的一个很好的解决方案?
【发布时间】:2013-05-13 15:10:29
【问题描述】:

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

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

但不幸的是,您不能在 catchfinally 块中使用 await。我了解到这是因为编译器没有任何方法可以返回 catch 块来执行您的 await 指令或类似指令之后的内容......

我尝试使用Task.Wait() 替换await,但遇到了死锁。我在网上搜索了如何避免这种情况,发现this site

由于我无法更改async 方法,也不知道它们是否使用ConfigureAwait(false),我创建了这些方法,它们采用Func&lt;Task&gt;,一旦我们在不同的线程上启动异步方法(以避免死锁)并等待其完成:

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

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

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

【问题讨论】:

  • 自 C# 6.0 起实际上允许在 catch 块中使用 await(请参阅下面的答案)
  • 相关 C# 5.0 错误消息:CS1985无法在 catch 子句的主体中等待。 CS1984不能在 finally 子句的主体中等待。

标签: c# .net exception-handling async-await c#-5.0


【解决方案1】:

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

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

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

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

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

【讨论】:

  • 谢谢你,但不幸的是我已经知道这个方法了。这就是我通常做的,但我不能在这里做。如果我只是在if 语句中使用throw exception;,堆栈跟踪将丢失。
【解决方案2】:

您可以将逻辑移到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 有什么好处(如Stephen Cleary 的回答)?
  • 我猜是如果你决定重新抛出Exception,你会失去之前的所有StackTrace
  • @VarvaraKalinina 完全正确。
【解决方案3】:

我们将hvd's great answer 提取到我们项目中的以下可重用实用程序类:

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)

【讨论】:

    【解决方案4】:

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

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

    新的 C# 6.0 功能,包括我刚才提到的are listed here 或视频here

    【讨论】:

    • 在 C# 6.0 中对 await in catch/finally blocks 的支持也在 Wikipedia 上有所说明。
    • @DavidRR。维基百科不具有权威性。就这一点而言,它只是数以百万计的另一个网站。
    • 虽然这对 C# 6 有效,但问题从一开始就被标记为 C# 5。这让我想知道这里有这个答案是否令人困惑,或者在这些情况下我们是否应该删除特定的版本标签。
    猜你喜欢
    • 2011-05-05
    • 2014-09-12
    • 2013-12-16
    • 2014-11-27
    • 1970-01-01
    • 1970-01-01
    • 2012-03-31
    • 1970-01-01
    • 2011-10-31
    相关资源
    最近更新 更多