IAsynsDisposable
时,我遇到了以下问题。问题的核心:如果
DisposeAsync
抛出异常,则此异常会隐藏在await using
块内抛出的任何异常。class Program
{
static async Task Main()
{
try
{
await using (var d = new D())
{
throw new ArgumentException("I'm inside using");
}
}
catch (Exception e)
{
Console.WriteLine(e.Message); // prints I'm inside dispose
}
}
}
class D : IAsyncDisposable
{
public async ValueTask DisposeAsync()
{
await Task.Delay(1);
throw new Exception("I'm inside dispose");
}
}
如果被抛出,那么会捕获
DisposeAsync
异常,只有在DisposeAsync
未抛出异常时才会捕获await using
内部的异常。然而,我更喜欢另一种方式:如果可能的话,从
await using
块中获取异常,仅当await using
块成功完成时才获取DisposeAsync
异常。原因是:假设我的类
D
使用某些网络资源并订阅远程通知。await using
内部的代码可能出现问题并导致通信渠道失败,此后尝试优雅地关闭通信的Dispose中的代码也将失败。但第一个异常给我提供了关于问题的真实信息,而第二个异常只是次要问题。在另一种情况下,当主要部分运行完毕且处置失败时,真正的问题在于
DisposeAsync
内部,因此来自DisposeAsync
的异常是相关的。这意味着仅抑制DisposeAsync
内部所有异常可能不是一个好主意。
我知道非异步情况下也存在相同的问题:在
finally
中的异常会覆盖try
中的异常,这就是为什么不建议在Dispose()
中抛出异常。但是对于访问网络的类,在关闭方法中抑制异常看起来一点也不好。
以下辅助程序可以解决这个问题:
static class AsyncTools
{
public static async Task UsingAsync<T>(this T disposable, Func<T, Task> task)
where T : IAsyncDisposable
{
bool trySucceeded = false;
try
{
await task(disposable);
trySucceeded = true;
}
finally
{
if (trySucceeded)
await disposable.DisposeAsync();
else // must suppress exceptions
try { await disposable.DisposeAsync(); } catch { }
}
}
}
并像这样使用{{它}}
await new D().UsingAsync(d =>
{
throw new ArgumentException("I'm inside using");
});
这有点丑陋(而且不允许在 using 块内使用早期返回等操作)。
是否有一个好的、规范的解决方案,如果可能的话,使用 await using
?我在互联网上的搜索甚至没有讨论过这个问题。
CloseAsync
尝试优雅地关闭事情,并在失败时抛出异常。DisposeAsync
只是尽力而为,并且会在失败时静默处理。 - canton7