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

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

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

而不是这样

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

usingawait using有什么不同?

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

21182 次浏览

经典同步

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

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

相当于

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

新的异步正在等待使用

新的等待使用调用并等待实现 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 IAsyncDisposable 接口

进去。NET,拥有非托管资源的类通常实现 一次性手机接口,以提供同步释放非托管资源的机制。然而,在某些情况下,他们需要 除了(或者代替)同步资源之外,还提供了释放非托管资源的异步机制。提供这样一种机制使使用者能够执行资源密集型的处理操作,而不会长时间阻塞 GUI 应用程序的主线程。

此接口的 一次性,一次性方法返回表示异步处理操作的 价值任务。拥有非托管资源的类实现此方法,这些类的使用者在不再需要此方法时对对象调用此方法。

Justin Lessard 的 回答解释了 usingawait using之间的区别,所以我将关注使用哪一个。有两种情况: 要么这两种方法 Dispose/DisposeAsync是互补的,或者他们正在做一些不同的事情。

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

  2. 如果这些方法执行的是不同的操作,您应该阅读文档,并决定哪种行为更适合手头的场景。让我们以实现两个接口(IDisposableIAsyncDisposable)的 System.Threading.Timer类为例。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线程上被调用) ,然后我们同步释放计时器。以下是 这个实验的输出:

Duration: 102 msec

现在让我们从 using切换到 await using。下面是 第二个实验的输出:

Duration: 1,005 msec

DisposeAsync的隐式调用返回一个仅在计时器回调完成后才完成的 ValueTask

因此,在 Timer的情况下,在 usingawait 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(); }

1 请注意,特别是在编写库时,默认情况下 ABC0捕获同步上下文。如果不希望这样做(这通常适用于库代码) ,则必须使用 ConfigureAwait(false)对其进行配置。这里讨论了一些含义: 如何正确使用“ waiting using”语法?