在切换到新的.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尽力而为,却默默地失败。