ChatGPT解决这个技术问题 Extra ChatGPT

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

I've noticed that in some case, Visual Studio recommends to do this

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

Instead of this

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

What is the difference between using and await using?

How should I decide which one to use?

It looks like you can only use await using with a IAsyncDisposable and you can only use using with a IDisposable since neither one inherits from the other. The only time you can use either is if the concrete class implements both and then it depends on if you are writing asynchronous code or not.

T
Theodor Zoulias

Classic sync using

Classic using calls the Dispose() method of an object implementing the IDisposable interface.

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

Is equivalent to

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

New async await using

The new await using calls and await the DisposeAsync() method of an object implementing the IAsyncDisposable interface.

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

Is equivalent to

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

The IAsyncDisposable Interface was added in .NET Core 3.0 and .NET Standard 2.1.

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.


Since using is syntactic sugar anyway, why not just make using handle both cases?
Probably to be explicit.
@Squirrelkiller Because they're not doing the same thing. If you have an object implementing both interfaces, do you dispose of it synchronously or asynchronously? What about an object only implementing IAsyncDisposable inside a non-async context?
T
Theodor Zoulias

Justin Lessard's answer explains the difference between using and await using, so I'll focus on which one to use. There are two cases: either the two methods Dispose/DisposeAsync are complementary, or they are doing something different.

If the methods are complementary, which is the common case, you can call either one of them and the result will be the same: the unmanaged resources will be released. There is no reason to call both of them, sequentially. If you do, the second call will be a no-op: the resources are already released, so there will be nothing more to do. Choosing which one to call is easy: if you are in a synchronous context, call the Dispose() (use the using). If you are in an asynchronous context, call the await DisposeAsync() (use the await using)¹. If the methods are doing something different, you should read the documentation and decide which behavior is more suitable for the scenario at hand. Let's talk for example for the System.Threading.Timer class, that implements both interfaces (IDisposable and IAsyncDisposable). The Dispose method releases the unmanaged resources as expected, but the DisposeAsync is doing something more than that: it also awaits the completion of any callbacks that are currently running. Let's make an experiment to demonstrate this difference:

var stopwatch = Stopwatch.StartNew();
using (new Timer(_ => Thread.Sleep(1000), null, 0, Timeout.Infinite))
{
    Thread.Sleep(100);
}
Console.WriteLine($"Duration: {stopwatch.ElapsedMilliseconds:#,0} msec");

We create a timer that fires after 0 msec, so practically immediately, then we wait for 100 msec to be sure that the callback has been invoked (it is invoked on a ThreadPool thread), and then we dispose the timer synchronously. Here is the output of this experiment:

Duration: 102 msec

Now let's switch from using to await using. Here is the output of the second experiment:

Duration: 1,005 msec

The implicit call to DisposeAsync returned a ValueTask that completed only after the completion of the timer's callback.

So in the case of a Timer, choosing between using and await using is not just a choice that depends on the context. You might prefer to use the synchronous using in an async context, because you don't care about the callback (you know that it's not harmful to let it become fire-and-forget). Or you might be in a synchronous context but you might prefer the behavior of await using, because fire-and-forget is unacceptable. In that case you'll have to abandon the convenience of using, and invoke instead the DisposeAsync explicitly in a finally block:

var timer = new Timer(_ => Thread.Sleep(1000), null, 0, Timeout.Infinite);
try { Thread.Sleep(100); }
finally { timer.DisposeAsync().AsTask().Wait(); }

¹ Be aware, especially if you are writing a library, that the await using captures the synchronization context by default. In case this is undesirable, which usually is for library code, you'll have to configure it with ConfigureAwait(false). This has some implications that are discussed here: How do I get the "await using" syntax correct?