在 try/catch/finally 中等待的一个好的解决方案?

我需要在 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());
}

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

当然,如果您有一些增强或者知道更好的方法,我洗耳恭听! :)

47634 次浏览

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.

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.

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).

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.