我注意到在某些情况下,Visual Studio 建议这样做
await using var disposable = new Disposable();
// Do something
而不是这个
using var disposable = new Disposable();
// Do something
using
和 await using
有什么区别?
我应该如何决定使用哪一个?
await using
与 IAsyncDisposable
一起使用,并且您只能将 using
与 IDisposable
一起使用,因为两者都不会从另一个继承。唯一可以使用的情况是具体类是否同时实现这两者,然后取决于您是否正在编写异步代码。
经典同步使用
经典 using 调用实现 IDisposable
接口的对象的 Dispose()
方法。
using var disposable = new Disposable();
// Do Something...
相当于
IDisposable disposable = new Disposable();
try
{
// Do Something...
}
finally
{
disposable.Dispose();
}
新的异步等待使用
新的 await using 调用和 await 实现 IAsyncDisposable
接口的对象的 DisposeAsync()
方法。
await using var disposable = new AsyncDisposable();
// Do Something...
相当于
IAsyncDisposable disposable = new AsyncDisposable();
try
{
// Do Something...
}
finally
{
await disposable.DisposeAsync();
}
IAsyncDisposable Interface 已添加到 .NET Core 3.0
和 .NET Standard 2.1
。
在 .NET 中,拥有非托管资源的类通常实现 IDisposable 接口,以提供一种同步释放非托管资源的机制。但是,在某些情况下,除了(或代替)同步资源之外,它们还需要提供一种异步机制来释放非托管资源。提供这样的机制使消费者能够在不长时间阻塞 GUI 应用程序的主线程的情况下执行资源密集型处置操作。此接口的 IAsyncDisposable.DisposeAsync 方法返回一个表示异步处置操作的 ValueTask。拥有非托管资源的类实现此方法,并且这些类的使用者在不再需要对象时调用此方法。
Justin Lessard 的 answer 解释了 using
和 await using
之间的区别,因此我将重点介绍使用哪一个。有两种情况:两种方法 Dispose
/DisposeAsync
是互补的,或者它们正在做不同的事情。
如果方法是互补的,这是常见的情况,你可以调用其中任何一个,结果都是一样的:非托管资源将被释放。没有理由依次调用它们。如果你这样做了,第二次调用将是空操作:资源已经释放,所以没有更多的事情可做。选择调用哪一个很容易:如果您在同步上下文中,请调用 Dispose()(使用 using)。如果您处于异步上下文中,请调用 await DisposeAsync() (使用 await 使用)¹。如果这些方法做了不同的事情,您应该阅读文档并决定哪种行为更适合手头的场景。让我们以 System.Threading.Timer 类为例,它实现了两个接口(IDisposable 和 IAsyncDisposable)。 Dispose 方法按预期释放非托管资源,但 DisposeAsync 所做的不止于此:它还等待当前正在运行的任何回调完成。让我们做一个实验来证明这种差异:
var stopwatch = Stopwatch.StartNew();
using (new Timer(_ => Thread.Sleep(1000), null, 0, Timeout.Infinite))
{
Thread.Sleep(100);
}
Console.WriteLine($"Duration: {stopwatch.ElapsedMilliseconds:#,0} msec");
我们创建一个在 0 毫秒后触发的计时器,实际上是立即触发,然后我们等待 100 毫秒以确保回调已被调用(它在 ThreadPool
线程上调用),然后我们同步处置计时器。这是 this experiment 的输出:
Duration: 102 msec
现在让我们从 using
切换到 await using
。这是 second experiment 的输出:
Duration: 1,005 msec
对 DisposeAsync
的隐式调用返回了仅在计时器回调完成后才完成的 ValueTask
。
因此,对于 Timer
,在 using
和 await using
之间进行选择不仅仅是取决于上下文的选择。您可能更喜欢在异步上下文中使用同步 using
,因为您不关心回调(您知道让它变成即发即弃并无害处)。或者您可能处于同步上下文中,但您可能更喜欢 await using
的行为,因为即发即弃是不可接受的。在这种情况下,您将不得不放弃 using
的便利性,而是在 finally
块中显式调用 DisposeAsync
:
var timer = new Timer(_ => Thread.Sleep(1000), null, 0, Timeout.Infinite);
try { Thread.Sleep(100); }
finally { timer.DisposeAsync().AsTask().Wait(); }
¹ 请注意,尤其是在您编写库时,await using
默认情况下会捕获同步上下文。如果不希望出现这种情况(通常用于库代码),则必须使用 ConfigureAwait(false)
对其进行配置。这有一些在这里讨论的含义:How do I get the "await using" syntax correct?
不定期副业成功案例分享
using
无论如何都是语法糖,为什么不直接使用处理这两种情况呢?IAsyncDisposable
的对象呢?