You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
As more and more asynchronous adoptions are made, the large amount of await and ConfigureAwait(false) in the code becomes a clear burden.
Motivation
Comparing the implementation of synchronization and asynchronous two ways, asynchronous way appears to be long and difficult to read and difficult to understand.
void M()
{
var result = M1().M2().M3();
using var disposableClass = new DisposableClass();
foreach (var item in new EnumerableClass()) {}
}
async Task M()
{
var result = await (await (await M1().ConfigureAwait(false)).M2().ConfigureAwait(false)).M3().ConfigureAwait(false);
var asyncDisposable = new AsyncDisposableClass();
await using asyncDisposable.ConfigureAwait(false);
var asyncEnumerable = new AsyncEnumerableClass();
await foreach (var item in asyncEnumerable.ConfigureAwait(false)) { }
}
Detailed design
Auto-complement await at compile time using AwaitAttribute or #await enable, and complete ConfigureAwait(false) at compile time using ConfigureAwaitAttribute or #configure_await false
[AttributeUsage(AttributeTargets.All, AllowMultiple = true)]
public class AwaitAttribute : Attribute
{
public string? Scope { get; set; }
public string? Target { get; set; }
}
#await enable
#await disable
#await restore
[AttributeUsage(AttributeTargets.All, AllowMultiple = true)]
public class ConfigureAwaitAttribute : Attribute
{
public bool ContinueOnCapturedContext { get; set; }
public string? Scope { get; set; }
public string? Target { get; set; }
}
#configure_await false
#configure_await true
#configure_await restore
A simple example
[Await]
async Task M()
{
var result1 = Task.FromResult(1); // equivalente var result1 = await Task.FromResult(1)
#await disable
var result2 = Task.FromResult(1); // result2 is Task<int>
#await restore
var result3 = Task.FromResult(1); // equivalente var result3 = await Task.FromResult(1)
}
[ConfigureAwait]
async Task M()
{
var result1 = await Task.FromResult(1); // equivalente var result1 = await Task.FromResult(1).ConfigureAwait(false)
#configure_await true
var result2 = await Task.FromResult(1);
#configure_await restore
var result3 = await Task.FromResult(1); // equivalente var result3 = await Task.FromResult(1).ConfigureAwait(false)
}
[Await, ConfigureAwait]
async Task M()
{
var result = Task.FromResult(1); // equivalente var result = await Task.FromResult(1).ConfigureAwait(false)
}
AwaitAttribute and #await enable only work within the method with async modifier
Detecting whether the target of the using statement implements the IAsyncDisposable interface, the IAsyncDisposable interface has been implemented with priority asynchronous method
class DisposableA : IDisposable {}
class DisposableB : IDisposable, IAsyncDisposable {}
[Await]
async Task M()
{
using var a = new DisposableA();
using var b = new DisposableB(); // equivalente await using var b = new DisposableB();
}
Compatible with explicit await using, tip suggests removal
class DisposableA : IDisposable {}
class DisposableB : IDisposable, IAsyncDisposable {}
[Await]
async Task M()
{
using var a = new DisposableA();
await using var b = new DisposableB();
}
Detecting whether the target of the foreach statement implements the IAsyncEnumerable interface, the IAsyncEnumerable interface has been implemented with priority asynchronous method
class EnumerableA : IEnumerable {}
class EnumerableB : IEnumerable, IAsyncEnumerable {}
[Await]
async Task M()
{
foreach (var item in new EnumerableA()) {}
foreach (var item in new EnumerableB()) {} // equivalente await foreach (var item in new EnumerableB()) {}
}
Compatible with explicit await foreach, tip suggests removal
class EnumerableA : IEnumerable {}
class EnumerableB : IEnumerable, IAsyncEnumerable {}
[Await]
async Task M()
{
foreach (var item in new EnumerableA()) {}
await foreach (var item in new EnumerableB()) {}
}
[assembly: Await] Effective within the project
[assembly: ConfigureAwait] Effective within the project
Scope and Target are defined in the same way as Scope and Target in SuperPressMessageAttribute, and can be defined to the namespace, type, and member levels
Going back to the example in Motivation, that could be rewritten with the use of [Await, ConfigureAwait]
[Await, ConfigureAwait]
async Task M()
{
var result = M1().M2().M3();
using var asyncDisposableClass = new AsyncDisposableClass();
foreach (var item in new AsyncEnumerableClass()) {}
}
If AwaitAttribute and ConfigureAwaitAttribute are declared globally, the pre-method [Await, ConfigureAwait] can also be omitted, and the code within the method remains as concise as it is synchronized.
The above approach is not suitable for all use scenarios, such as the basic class library needs better control of Task, and it is more intuitive to use explicit await when there are not many asynchronous uses. But the advantage of it is that it's optional, doesn't introduce new instruction, doesn't break existing code, and can be ignored when you don't think it's appropriate, using it only in clear and appropriate scenarios (especially for enterprise applications with a large number of repeated CRUD and API calls).
Drawbacks
The return value type and the return type in the method definition are inconsistent.
The implementation is heavy and difficult
Unresolved questions
The above idea is one of the beautiful wishes of coding life and is not carefully considered.
The text was updated successfully, but these errors were encountered:
Implicit await and ConfigureAwait
Summary
As more and more asynchronous adoptions are made, the large amount of await and ConfigureAwait(false) in the code becomes a clear burden.
Motivation
Comparing the implementation of synchronization and asynchronous two ways, asynchronous way appears to be long and difficult to read and difficult to understand.
Detailed design
Auto-complement await at compile time using AwaitAttribute or #await enable, and complete ConfigureAwait(false) at compile time using ConfigureAwaitAttribute or #configure_await false
A simple example
Going back to the example in Motivation, that could be rewritten with the use of [Await, ConfigureAwait]
If AwaitAttribute and ConfigureAwaitAttribute are declared globally, the pre-method [Await, ConfigureAwait] can also be omitted, and the code within the method remains as concise as it is synchronized.
The above approach is not suitable for all use scenarios, such as the basic class library needs better control of Task, and it is more intuitive to use explicit await when there are not many asynchronous uses. But the advantage of it is that it's optional, doesn't introduce new instruction, doesn't break existing code, and can be ignored when you don't think it's appropriate, using it only in clear and appropriate scenarios (especially for enterprise applications with a large number of repeated CRUD and API calls).
Drawbacks
Unresolved questions
The above idea is one of the beautiful wishes of coding life and is not carefully considered.
The text was updated successfully, but these errors were encountered: