ChatGPT解决这个技术问题 Extra ChatGPT

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

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

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

而不是这个

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

usingawait using 有什么区别?

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

看起来您只能将 await usingIAsyncDisposable 一起使用,并且您只能将 usingIDisposable 一起使用,因为两者都不会从另一个继承。唯一可以使用的情况是具体类是否同时实现这两者,然后取决于您是否正在编写异步代码。

T
Theodor Zoulias

经典同步使用

经典 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。拥有非托管资源的类实现此方法,并且这些类的使用者在不再需要对象时调用此方法。


既然 using 无论如何都是语法糖,为什么不直接使用处理这两种情况呢?
可能是明确的。
@Squirrelkiller 因为他们没有做同样的事情。如果您有一个实现两个接口的对象,您是同步还是异步处理它?仅在非异步上下文中实现 IAsyncDisposable 的对象呢?
T
Theodor Zoulias

Justin Lessard 的 answer 解释了 usingawait 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,在 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(); }

¹ 请注意,尤其是在您编写库时,await using 默认情况下会捕获同步上下文。如果不希望出现这种情况(通常用于库代码),则必须使用 ConfigureAwait(false) 对其进行配置。这有一些在这里讨论的含义:How do I get the "await using" syntax correct?