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

Jus*_*ard 4 c# c#-8.0

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

await using var disposable = new Disposable();
// Do something
Run Code Online (Sandbox Code Playgroud)

代替这个

using var disposable = new Disposable();
// Do something
Run Code Online (Sandbox Code Playgroud)

using和之间有什么区别await using

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

The*_*ias 30

Justin Lessard 的回答using解释了和之间的区别await using,因此我将重点讨论使用哪一个。有两种情况:要么这两种方法DisposeDisposeAsync互补的,要么它们在做不同的事情。

\n
    \n
  1. 如果这些方法是互补的(这是常见情况),您可以调用其中任何一个,结果将是相同的:非托管资源将被释放。没有理由依次调用它们两个。如果这样做,第二次调用将是无操作:资源已被释放,因此将不再需要执行任何操作。选择要调用的方法很容易:如果您处于同步上下文中,请调用Dispose()(使用using)。如果您处于异步上下文中,请调用await DisposeAsync()(使用await using)\xc2\xb9。

    \n
  2. \n
  3. 如果这些方法执行不同的操作,您应该阅读文档并决定哪种行为更适合当前的场景。例如,让我们讨论一下System.Threading.Timer实现两个接口(IDisposableIAsyncDisposable)的类。该Dispose方法按预期释放非托管资源,但它DisposeAsync所做的还不止于此:它还等待当前正在运行的任何回调的完成。让我们做一个实验来证明这种差异:

    \n
  4. \n
\n
var stopwatch = Stopwatch.StartNew();\nusing (new Timer(_ => Thread.Sleep(1000), null, 0, Timeout.Infinite))\n{\n    Thread.Sleep(100);\n}\nConsole.WriteLine($"Duration: {stopwatch.ElapsedMilliseconds:#,0} msec");\n
Run Code Online (Sandbox Code Playgroud)\n

我们创建一个在 0 毫秒后触发的计时器,实际上是立即触发,然后我们等待 100 毫秒以确保回调已被调用(它在线程上调用ThreadPool),然后我们同步处置计时器。这是该实验的输出:

\n
var stopwatch = Stopwatch.StartNew();\nusing (new Timer(_ => Thread.Sleep(1000), null, 0, Timeout.Infinite))\n{\n    Thread.Sleep(100);\n}\nConsole.WriteLine($"Duration: {stopwatch.ElapsedMilliseconds:#,0} msec");\n
Run Code Online (Sandbox Code Playgroud)\n

现在让我们从 切换usingawait using这是第二个实验的输出

\n
Duration: 102 msec\n
Run Code Online (Sandbox Code Playgroud)\n

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

\n

因此,在 a 的情况下,在和 之间Timer进行选择不仅仅是一个取决于上下文的选择。您可能更喜欢在异步上下文中使用同步,因为您不关心回调(您知道让它变得“即发即忘”并没有什么害处)。或者您可能处于同步上下文中,但您可能更喜欢 的行为,因为“即发即弃”是不可接受的。在这种情况下,您将不得不放弃 , 的便利性,并在块中显式调用:usingawait usingusingawait usingusingDisposeAsyncfinally

\n
var timer = new Timer(_ => Thread.Sleep(1000), null, 0, Timeout.Infinite);\ntry { Thread.Sleep(100); }\nfinally { timer.DisposeAsync().AsTask().Wait(); }\n
Run Code Online (Sandbox Code Playgroud)\n

\xc2\xb9请注意,特别是如果您正在编写库,await using默认情况下会捕获同步上下文。如果这是不需要的(通常是针对库代码),您必须使用ConfigureAwait(false). 这有一些含义,这里讨论:How do I get the “await using” 语法正确?

\n


Jus*_*ard 13

经典同步使用

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

using var disposable = new Disposable();
// Do Something...
Run Code Online (Sandbox Code Playgroud)

相当于

IDisposable disposable = new Disposable();
try
{
    // Do Something...
}
finally
{
    disposable.Dispose();
}
Run Code Online (Sandbox Code Playgroud)

新的异步等待使用

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

await using var disposable = new AsyncDisposable();
// Do Something...
Run Code Online (Sandbox Code Playgroud)

相当于

IAsyncDisposable disposable = new AsyncDisposable();
try
{
    // Do Something...
}
finally
{
    await disposable.DisposeAsync();
}
Run Code Online (Sandbox Code Playgroud)

IAsyncDisposable接口在加.NET Core 3.0.NET Standard 2.1

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

此接口的IAsyncDisposable.DisposeAsync方法返回一个ValueTask,它表示异步处置操作。拥有非托管资源的类实现此方法,并且这些类的使用者在不再需要该对象时在对象上调用此方法。

  • 既然“using”无论如何都是语法糖,为什么不让 using 处理这两种情况呢? (8认同)
  • @Squirrelkiller 因为他们没有做同样的事情。如果您有一个实现两个接口的对象,您是同步还是异步处理它?只在非异步上下文中实现“IAsyncDisposable”的对象怎么样? (7认同)
  • 大概是为了明确。 (2认同)