IAsyncDisposable, using statements, and async/await

Topics: C# Language Design
May 23, 2014 at 2:31 PM
Edited May 24, 2014 at 2:21 AM
With the introduction of support for await inside of a finally block, I'd like to propose the following extension to the using statement in C#.

The System.IAsyncDisposable interface

public interface IAsyncDisposable : IDisposable
{
    Task DisposeAsync();
}

Modification to the using statement

When a using statement appears inside of an async method, the translation of the using statement is modified to the following.

A using statement of the form
using (ResourceType resource = expression) statement
corresponds to one of four possible expansions. When ResourceType is a non-nullable value type which implements System.IAsyncDisposable, the expansion is
{
    ResourceType resource = expression;
    try {
        statement;
    }
    finally {
        await ((IAsyncDisposable)resource).DisposeAsync();
    }
}
Otherwise, when ResourceType is a non-nullable value type which does not implement System.IAsyncDisposable, the expansion is
{
    ResourceType resource = expression;
    try {
        statement;
    }
    finally {
        ((IDisposable)resource).Dispose();
    }
}
Otherwise, when ResourceType is a nullable value type or a reference type other than dynamic, the expansion is:
{
    ResourceType resource = expression;
    try {
        statement;
    }
    finally {
        if (resource != null) {
            IAsyncDisposable tmp = resource as IAsyncDisposable;
            if (tmp != null) {
                await tmp.DisposeAsync();
            }
            else {
                ((IDisposable)resource).Dispose();
            }
        }
    }
}
Otherwise, when ResourceType is dynamic, the expansion is
{
    ResourceType resource = expression;
    IDisposable d = (IDisposable)resource;
    try {
        statement;
    }
    finally {
        if (d != null) {
            IAsyncDisposable tmp = d as IAsyncDisposable;
            if (tmp != null) {
                await tmp.DisposeAsync();
            }
            else {
                d.Dispose();
            }
        }
    }
}
The IAsyncDisposable interface has no impact on the expansion of using statements which appear in any context other than a method marked with the async modifier.
May 23, 2014 at 10:13 PM
Instead of modifying the current using statement (which now is exclusive for IDisposable), why not having a new await using statement (which will be exclusive for IAsyncDisposable)?
await using (ResourceType resource = expression) statement
May 23, 2014 at 10:42 PM
The expansions for non-nullable value type have a bug.
await ((IAsyncDisposable)resource).DisposeAsync();
and
((IDisposable)resource).Dispose();
You are boxing resource. Boxing makes a copy, and it is the copy which is disposed.
May 24, 2014 at 1:54 AM
drowa wrote:
The expansions for non-nullable value type have a bug.
await ((IAsyncDisposable)resource).DisposeAsync();
and
((IDisposable)resource).Dispose();
You are boxing resource. Boxing makes a copy, and it is the copy which is disposed.
The syntax is modeled after the equivalent expansion described in the C# Language Specification §8.13. The cast is required in order for the expansion to call the correct method whether or not the value type explicitly implements the IDisposable.Dispose() method. In this case, the cast is only used to ensure the correct method is invoked and no boxing operation takes place.

The primary difference between the non-nullable value type handling and the handling of nullable value types and reference types is due to the fact that using an intermediate variable of type IDisposable would in fact introduce an unnecessary boxing operation, where the inline cast with the call does not.
May 24, 2014 at 1:59 AM
drowa wrote:
Instead of modifying the current using statement (which now is exclusive for IDisposable), why not having a new await using statement (which will be exclusive for IAsyncDisposable)?
await using (ResourceType resource = expression) statement
No new syntax is required for the feature because the alteration does not affect code written before the change takes place. The concept of asynchronous operations within an async method is easy for users to understand, and the overall behavior of the resource cleanup in the using block is changed only by the fact that the operation is performed as a continuation task instead of a synchronous call.

Separating the behavior would also result in a poor experience for users working with dynamic types.
May 24, 2014 at 2:26 AM
Note that I also considered the following signature for DisposeAsync:
Task DisposeAsync(CancellationToken cancellationToken);
In the end, I chose to avoid introducing the CancellationToken for the following reasons:
  1. It would require further extending the using statement syntax to provide a CancellationToken, resulting in a more complicated implementation that affects larger portions of the compilation process (including the parser and syntax trees).
  2. When necessary, support for cancellation can be provided through other means, including but not limited to writing the resource class in such a way that a CancellationToken can be specified during resource acquisition for use later during the implementation of DisposeAsync.
The proposal described above is simpler than one involving CancellationToken and does not prevent users from providing support for cancellation in their use of the new interface.
May 24, 2014 at 3:27 AM
drowa wrote:
You are boxing resource. Boxing makes a copy, and it is the copy which is disposed.
The C# compiler generally avoids the boxing when the type is a value type, but still makes a redundant copy and disposes that, thus causing the CPU to spend extra time copying something while precluding any usage scenario that would require the value type to make any modifications to itself upon disposal. The only situations in which using can be employed with value types are either those where it does nothing but is implemented for some other reason (e.g. because IEnumerator<T> requires it) or those in which the value type serves as a wrapper around an immutable reference to a class object, and calling Dispose on the wrapper is equivalent to calling Dispose on the wrapped object.
May 24, 2014 at 10:57 AM
I like the idea, though I feel it is a bit edge-case for a general language support -- I used/seen async disposables several times, but nowhere near the amount of disposables I used.
 
The IAsyncDisposable interface has no impact on the expansion of using statements which appear in any context other than a method marked with the async modifier.
This feels too implicit. So if an object implements both interfaces, the usage would be switched implicitly based on the presence of the async modifier on containing method?
I would prefer some explicit syntax (async using?) to make sure removing IAsyncDisposable interface from the class does not implicitly switch all usings to sync.

One other alternative would be to somehow extend using syntax to allow you to specify the finally part in cases when object is not IDisposable -- which would have the benefit of shorter try/finally in cases when you do not have IDisposable implementation at hand.
May 24, 2014 at 4:44 PM
Edited May 24, 2014 at 4:45 PM
ashmind wrote:
I like the idea, though I feel it is a bit edge-case for a general language support -- I used/seen async disposables several times, but nowhere near the amount of disposables I used.
I see I failed to include the motivating example which led me to make this request. In one library I'm working on, a disposable resource is actually a handle to a network resource which is acquired and released through a REST API. Code using this resource would ideally be written as follows:
using (Resource resource = await AcquireResourceAsync()) {
    ...
}
While it is completely straightforward to asynchronously acquire a resource, unfortunately, under the current implementation, there is no way to leverage a using statement to asynchronously release a resource when working with network resources and the recommended "async all-the-way-down" approach. As it turns out, IDisposable.Dispose and IAsyncDisposable.DisposeAsync perform exactly the same underlying operation, but DisposeAsync provides substantial benefits to users working with resources where non-deterministic latencies apply.

Due to this limitation, we recommend that users structure their code as follows:
using (Resource resource = await AcquireResourceAsync()) {
    // use the resource

    // release the resource
    await resource.DisposeAsync();
}
In accordance with recommended practices for implementing IDisposable, the call to Dispose in this case is a no-op.
May 24, 2014 at 4:47 PM
Edited May 24, 2014 at 4:49 PM
ashmind wrote:
The IAsyncDisposable interface has no impact on the expansion of using statements which appear in any context other than a method marked with the async modifier.
This feels too implicit. So if an object implements both interfaces, the usage would be switched implicitly based on the presence of the async modifier on containing method?
I would prefer some explicit syntax (async using?) to make sure removing IAsyncDisposable interface from the class does not implicitly switch all usings to sync.
The core semantics of Dispose and DisposeAsync are equivalent. Removing the IAsyncDisposable interface from a class would switch all using statements in async methods to calling Dispose, but resources would still be properly released.

This proposal centers entirely around supporting async all-the-way-down for efficiency and reliability of applications, not around providing two separate concepts of releasing resources owned by an instance.
May 25, 2014 at 5:20 AM
sharwell wrote:
In accordance with recommended practices for implementing IDisposable, the call to Dispose in this case is a no-op.
Just to be clear, it is no-op assuming no unhandled exception occurs inside using; synchronous disposing is a fallback. It is a workaround for the lack of support for await inside of a finally block.
May 25, 2014 at 6:24 AM
Edited May 25, 2014 at 6:27 AM
sharwell wrote:
Code using this resource would ideally be written as follows:
using (Resource resource = await AcquireResourceAsync()) {
    ...
}
This code make me think again about a await using statement exclusive for AsyncDisposable.

Omitting details about dynamic types and value types, roughly speaking, a await using statement of the form:
await using (ResourceType resource = expression) statement
Where expression is implicitly convertible to Task<TResult>, TResult is implicitly convertible to ResourceType, and ResourceType is implicitly convertible to IAsyncDisposable, the expansion is:
{
    ResourceType resource = await expression;
    try {
        statement;
    }
    finally {
        await ((IAsyncDisposable)resource).DisposeAsync();
    }
}
My reasoning is if ResourceType is IAsyncDisposable then we will most likely have a way to acquire it asynchronously.

In this way the first code in this post could be written as follow:
await using (Resource resource = AcquireResourceAsync()) {
    ...
}
The point is, or you acquire and dispose synchronously (using) or you acquire and dispose asynchronously (await using). You can only have a mix with finally blocks.

Note await using can only be used from a async method.

This would also resolve @ashmind concern about too much implicit.
May 25, 2014 at 3:35 PM
drowa wrote:
sharwell wrote:
In accordance with recommended practices for implementing IDisposable, the call to Dispose in this case is a no-op.
Just to be clear, it is no-op assuming no unhandled exception occurs inside using; synchronous disposing is a fallback. It is a workaround for the lack of support for await inside of a finally block.
Of course, but that's a well-understood semantic detail that's not relevant to this proposal discussion.
May 25, 2014 at 5:05 PM
Edited May 25, 2014 at 5:05 PM
I have to admit this part on my suggestion
Resource resource = AcquireResourceAsync()
doesn't smell good.
May 25, 2014 at 5:21 PM
Edited May 25, 2014 at 5:22 PM
The original proposal is better indeed.

It should be obvious, if we are using using with a AsyncDisposable resource from inside a async method, our intention is to dispose it asynchronously. I think that is the intuition of @sharwell. A probable await appearing in expression would make that even more obvious.

So, disregard my suggestion.