一个很好的解决方案,等待在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;
}
但不幸的是,你不能在catch
或finally
块中使用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());
}
所以我的问题是:你认为这个代码是好的吗?
当然,如果你有一些改进或知道更好的方法,我在听!:)
您可以通过使用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开始,可以在catch
和finally
块中使用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)
。