我需要在async
块中调用catch
方法,然后再次抛出异常(带有堆栈跟踪),如下所示:
try
{
// Do something
}
catch
{
// <- Clean things here with async methods
throw;
}
但遗憾的是,您无法在await
或catch
块中使用finally
。我学会了它,因为编译器没有任何方法可以返回catch
块来执行await
指令后面的内容......
我尝试使用Task.Wait()
替换await
,我遇到了僵局。我在网上搜索了如何避免这种情况并找到了this site。
由于我无法更改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());
}
所以我的问题是:你觉得这段代码还可以吗?
当然,如果您有一些改进或知道更好的方法,我会倾听! :)
答案 0 :(得分:168)
您可以将逻辑移到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 :(得分:53)
您应该知道,自C#6.0以来,可以在await
和catch
块中使用finally
,所以您实际上可以这样做:
try
{
// Do something
}
catch (Exception ex)
{
await DoCleanupAsync();
throw;
}
新的C#6.0功能,包括我刚才提及的are listed here或视频here。
答案 2 :(得分:15)
如果您需要使用async
错误处理程序,我建议使用以下内容:
Exception exception = null;
try
{
...
}
catch (Exception ex)
{
exception = ex;
}
if (exception != null)
{
...
}
同步阻塞async
代码(无论它运行的是什么线程)的问题在于您同步阻塞。在大多数情况下,最好使用await
。
更新:由于您需要重新抛出,可以使用ExceptionDispatchInfo
。
答案 3 :(得分: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)
。