使用和等待使用有什么区别?我如何决定使用哪一个?

What is the difference between using and await using? And how can I decide which one to use?

我注意到在某些情况下,Visual Studio 建议这样做

await using var disposable = new Disposable();
// Do something

而不是这个

using var disposable = new Disposable();
// Do something

usingawait using有什么区别?

我应该如何决定使用哪一个?

经典同步使用

Classic using 调用实现 IDisposable 接口的对象的 Dispose() 方法。

using var disposable = new Disposable();
// Do Something...
    

相当于

IDisposable disposable = new Disposable();
try
{
    // Do Something...
}
finally
{
    disposable.Dispose();
}

新的异步等待使用

新的 await using 调用并等待实现 IAsyncDisposable 接口的对象的 DisposeAsync() 方法。

await using var disposable = new AsyncDisposable();
// Do Something...
    

相当于

IAsyncDisposable disposable = new AsyncDisposable();
try
{
    // Do Something...
}
finally
{
    await disposable.DisposeAsync();
}

.NET Core 3.0.NET Standard 2.1中添加了IAsyncDisposable Interface

In .NET, classes that own unmanaged resources usually implement the IDisposable interface to provide a mechanism for releasing unmanaged resources synchronously. However, in some cases they need to provide an asynchronous mechanism for releasing unmanaged resources in addition to (or instead of) the synchronous one. Providing such a mechanism enables the consumer to perform resource-intensive dispose operations without blocking the main thread of a GUI application for a long time.

The IAsyncDisposable.DisposeAsync method of this interface returns a ValueTask that represents the asynchronous dispose operation. Classes that own unmanaged resources implement this method, and the consumer of these classes calls this method on an object when it is no longer needed.

Justin Lessard 的 解释了 usingawait using 之间的区别,因此我将重点介绍使用哪一个。有两种情况:两种方法 Dispose/DisposeAsync 是互补的,或者它们在做不同的事情。

  1. 如果这些方法是互补的,这是常见的情况,您可以调用其中任何一个,结果都是一样的:非托管资源将被释放。没有理由按顺序调用它们。如果这样做,第二次调用将是 no-op:资源已经释放,因此无需再做任何事情。选择调用哪一个很容易:如果您处于同步上下文中,请调用 Dispose()(使用 using)。如果您处于异步上下文中,请调用 await DisposeAsync()(使用 await using)¹。

  2. 如果这些方法做的事情不同,您应该阅读文档并确定哪种行为更适合手头的场景。让我们以实现两个接口(IDisposableIAsyncDisposable)的 System.Threading.Timer class 为例。 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 thread), and then we dispose the timer synchronously. Here is the output of this experiment:

Duration: 102 msec

现在让我们从 using 切换到 await using。这是 second experiment:

的输出
Duration: 1,005 msec

DisposeAsync 的隐式调用返回了 ValueTask,仅在计时器回调完成后才完成。

所以在 Timer 的情况下,在 usingawait using 之间进行选择不仅仅是一个取决于上下文的选择。您可能更喜欢在异步上下文中使用同步 using,因为您不关心回调(您知道让它变成 fire-and-forget 并无害处)。或者您可能处于同步上下文中,但您可能更喜欢 await using 的行为,因为 fire-and-forget 是不可接受的。在那种情况下,您将不得不放弃 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) 对其进行配置。这有一些在这里讨论的含义: