一个好的尝试/捕获/最终解决方案?


92

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

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

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

我试图用它Task.Wait()来替换await,但陷入僵局。我在网上搜索了如何避免这种情况的方法,并找到了该站点

由于我无法更改async方法,也无法知道它们是否在使用ConfigureAwait(false),因此创建了这些方法,这些方法采用a 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());
}

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

当然,如果您进行了一些增强或了解更好的方法,那么我在听!:)


2
await自C#6.0起,实际上允许在catch块中使用(请参见下面的答案)
Adi Lester 2014年

3
相关的C#5.0错误消息:CS1985无法在catch子句的主体中等待。 CS1984无法在finally子句的正文中等待。
DavidRR

Answers:


172

您可以将逻辑移出该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抛出异常的位置。


1
保留ExceptionDispatchInfo而不是保留Exception(如Stephen Cleary的回答)的优点是什么?
Varvara Kalinina

2
我可以猜测,如果您决定将其扔掉Exception,则会失去以前所有的东西StackTrace
Varvara Kalinina

1
@VarvaraKalinina正是。

54

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

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

新的C#6.0的功能,包括我刚才提到的一个在这里列出或为视频在这里


Wikipedia上也指出了对C#6.0 中catch / finally块等待的支持。
DavidRR

4
@DavidRR。维基百科不具有权威性。就此而言,它只是数以百万计的另一个网站。
user34660 '17

尽管这对于C#6有效,但从一开始就将该问题标记为C#5。这使我想知道这里是否有这个答案会令人困惑,或者在这种情况下我们是否应该删除特定的版本标签。
julealgon

16

如果您需要使用async错误处理程序,建议您使用以下方法:

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

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

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

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


1
谢谢,但是很不幸,我已经知道这种方法了。那就是我通常要做的,但是我不能在这里做。如果仅throw exception;if语句中使用,堆栈跟踪将丢失。
user2397050 2013年

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)

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.