一个很好的解决方案,等待在try/catch/finally

本文关键字:等待 try catch finally 解决方案 一个 很好 | 更新日期: 2023-09-27 18:14:18

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

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

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

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

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

一个很好的解决方案,等待在try/catch/finally

您可以通过使用ExceptionDispatchInfo将逻辑移出catch块并在需要时重新抛出异常。

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中的哪个位置被抛出。

你应该知道,从c# 6.0开始,可以在catchfinally块中使用await,所以你实际上可以这样做:

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

c# 6.0的新特性,包括我刚才提到的特性,在这里列出了,或者在这里作为视频。

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

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

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

Update:因为你需要重新抛出,你可以使用ExceptionDispatchInfo .

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