在切换到新的 .NET Core 3 的过程IAsynsDisposable
中,我偶然发现了以下问题。
问题的核心:如果DisposeAsync
抛出异常,这个异常隐藏了await using
-block内部抛出的任何异常。
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
-exception 如果它被抛出,并且await using
只有内部的异常DisposeAsync
没有抛出。
但是,我更喜欢另一种方式:await using
如果可能,从块中获取异常,并且 -DisposeAsync
仅当await using
块成功完成时才出现异常。
理由:假设我的班级D
使用一些网络资源并订阅了一些远程通知。里面的代码await using
可能会出错并导致通信通道失败,然后在 Dispose 中尝试优雅地关闭通信(例如,取消订阅通知)的代码也会失败。但是第一个例外为我提供了有关问题的真实信息,而第二个例外只是次要问题。
在另一种情况下,当 main 部分运行并且处理失败时,真正的问题在 inside DisposeAsync
,因此异常 fromDisposeAsync
是相关的。这意味着仅仅抑制内部的所有异常DisposeAsync
不应该是一个好主意。
我知道非异步 case 也存在同样的问题:异常 infinally
覆盖了异常 in try
,这就是为什么不建议 throw in 的原因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
?我在互联网上的搜索甚至没有发现讨论这个问题。