在切换到新的.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");
}
}
被捕获的是AsyncDispose
-exception(如果引发了),并且await using
只有AsyncDispose
不引发时才从内部发出异常。
但是,我还是更喜欢它:await using
如果可能的话,从块中获取异常,并且DisposeAsync
仅在await using
块成功完成时才使用-exception 。
基本原理:想象一下,我的班级D
使用一些网络资源并远程订阅了一些通知。内部的代码await using
可能会出错,并导致通信通道失败,此后,Dispose中试图正常关闭通信(例如,取消订阅通知)的代码也会失败。但是第一个例外为我提供了有关该问题的真实信息,第二个例外只是次要问题。
在另一种情况下,当主要部分通过并且处置失败时,真正的问题就在内部DisposeAsync
,因此相关的例外DisposeAsync
。这意味着仅仅抑制内部的所有异常DisposeAsync
并不是一个好主意。
我知道非异步情况也存在相同的问题:in中finally
的exception覆盖in中的exception try
,这就是为什么不建议使用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
如果可能的话?我在互联网上的搜索甚至都没有找到讨论此问题的信息。
CloseAsync
意味着我需要采取额外的预防措施来使其运行。如果我只是将其放在using
-block 的末尾,它将在早期返回等(这就是我们想要发生的事情)和异常(这就是我们想要发生的事情)上被跳过。但是这个想法看起来很有希望。
Dispose
一直是“事情可能出了问题:尽力改善情况,但不要让情况变得更糟”,我看不出为什么AsyncDispose
会有什么不同。
DisposeAsync
尽最大努力整理而不丢球是正确的事情。您所谈论的是有意提早返回,其中有意提早返回可能会错误地绕过对的调用CloseAsync
:这些是许多编码标准所禁止的。
Close
方法,正是出于这个原因。做同样的CloseAsync
事可能是明智的:尝试很好地关闭事物并抛出失败。DisposeAsync
尽力而为,却默默地失败。