[Suggestion] Don't require "async" keyword for the methods with "awaits" in the body

Topics: C# Language Design
Jan 6, 2015 at 9:03 PM
This is misleading. This looks like a part of method signature but this is not. The only thing it does is: it enables awaits in the method body.
The "async" tells nothing new for the caller - the caller already knows that the method returns Task. If the method returns "void" - anyways any methods can start threads, enqueue work items to the ThreadPoll etc. Someone could think "the method is marked as async, so this is called in a separate thread", but that is wrong!

Suggestion:
  1. Don't require "async" and show a warning "This is obsolete".
  2. Add a compiler option to disable "await" keyword (for the projects migrating from old .net versions and which use methods, classes, ... with the name "await").
  3. Add an attribute [EnableAwait(true|false)] to enable or disable await for some method or class.
Of course you can disable await by the compiler option but enable it for a certain method or a class by the attribute. And you can access the classes, methods etc. with the name "await" using @: "new @await(arg);". This is allowed even in the context where the await keyword is enabled.

(The suggestion has been initially posted on uservoice)
Jan 7, 2015 at 1:13 AM
artelk wrote:
This is misleading. This looks like a part of method signature but this is not. The only thing it does is: it enables awaits in the method body.
It kind of is part of the method signature. Only for the source code, of course.

artelk wrote:
The "async" tells nothing new for the caller - the caller already knows that the method returns Task. If the method returns "void" - anyways any methods can start threads, enqueue work items to the ThreadPoll etc. Someone could think "the method is marked as async, so this is called in a separate thread", but that is wrong!
Isn't that great that you can await on entirely differently built things?

People are already think that. But that's not due to the language constructs but to lack of knowledge and reading the documentation.

Asynchronous equals/implies threads is false!

Asynchronous equals/implies concurrent is false!

artelk wrote:
Suggestion:
  1. Don't require "async" and show a warning "This is obsolete".
  2. Add a compiler option to disable "await" keyword (for the projects migrating from old .net versions and which use methods, classes, ... with the name "await").
  3. Add an attribute [EnableAwait(true|false)] to enable or disable await for some method or class.
Of course you can disable await by the compiler option but enable it for a certain method or a class by the attribute. And you can access the classes, methods etc. with the name "await" using @: "new @await(arg);". This is allowed even in the context where the await keyword is enabled.

(The suggestion has been initially posted on uservoice)
What you are proposing is for async-await to be like iterators. There's no iterator keyword, just yield. Except there is. In Visual Basic. And, because of that Visual Basic has iterator lambdas and C# doesn't.

As to your suggestion of triggering await at will by a "simple" compiler switch, how do you purpose that to be implemented?

Imagine this method:
/* async removed as per your proposal */ Task<bool> GetBoolAsync()
{
    return await Task.Run(() => GetBool());
}
"Disabling" await would mean that the calling code would be calling something expected to be asynchronous (as the suffix announces it) that returns an awaiter (might be a Task or not). How would the compiler handle it?

How would the compiler change the behavior of Task.Run, or any other method in any other third party library?
Jan 7, 2015 at 1:48 AM
Read this article by Eric Lippert:
http://blogs.msdn.com/b/ericlippert/archive/2010/09/27/ambiguous-optional-parentheses-part-three.aspx

It gives pretty good reasons for having the async keyword
Jan 7, 2015 at 1:57 AM
Pulling some links from my blog ( http://blog.stephencleary.com/2011/09/async-ctp-why-do-keywords-work-that-way.html ):

Eric Lippert's post is the definitive answer: http://blogs.msdn.com/b/ericlippert/archive/2010/11/11/whither-async.aspx
This same question has also been discussed endlessly in these locations:
http://blogs.msdn.com/b/ericlippert/archive/2010/10/29/asynchronous-programming-in-c-5-0-part-two-whence-await.aspx
http://channel9.msdn.com/Forums/Coffeehouse/Why-is-the-async-keyword-needed
http://social.msdn.microsoft.com/Forums/en-US/async/thread/75493675-4a39-4958-a493-ad8a96f8a19d
http://stackoverflow.com/questions/9225748/why-does-the-async-keyword-exist

I'm against an attribute defining new language syntax. It's not at all clear to me that "[EnableAwait(true)]" is better than "async". IMO, "async" is clearer both for the compiler and human readers.

Not to mention the huge backwards-compatibility burden this would impose.
Jan 7, 2015 at 9:48 AM
Edited Jan 7, 2015 at 10:56 AM
The feature is named "async/await" but the only "await" is a feature; the "async" is just a keyword enabling the "await" feature.
So this is misleading even at the feature naming level. It seems they've made this intentionally misleading to make asynchronous programming a bit more difficult. ;)
The olny reason for the "async" keyword I know is "what if sombody use 'await' as a class\method... name; there would be a problem when the project is compiled by the new compiler". I suppose it is quite probable that there are no such projects in the world. At least I don't think there are more than 10 such projects in the world.
So the "async" keyword at the method signature level looks like a TOO big gun for this!
I think the compiler option for such exceptional projects would be much better than the annoying and misleading "async" keyword in 99.999% projects.
Jan 7, 2015 at 9:52 AM
Edited Jan 7, 2015 at 11:17 AM
StephenCleary,
The [EnableAwait(true)] is only needed for those exceptional projects which have classes\methods... named "await". You and I have never seen such projects, am I right?
Jan 7, 2015 at 10:49 AM
Edited Jan 7, 2015 at 11:11 AM
Imagine you have a project which you are migrating to the new .net with the new compiler.

Situation 1: There are no classes/methords/fields/variables named "await" in the project (I believe this is true for more than 99.99% projects) => no problems with compilation. After migration you can add some methods returning Tasks (or other awaitables) which use the new "await" feature.

Situation 2: You use "await" as a name of something. You have the following options:
  1. Specify the compiler option disabling the "await" feature. After migration you can use the new "await" feature but you should add [EnableAwait(true)] for the methods or classes (that enables this for all their methods). To access the existing classes/methods/... named "await" from the contexts where the "await" feature is enabled you use '@' prefix: '@await'.
  2. Add [EnableAwait(false)] where you use the "await" classes/methods/... => After migration you can add some methods returning Tasks (or other awaitables) which use the new "await" feature. To access the existing classes/methods/... named "await" from the contexts where the "await" feature isn't disabled you use '@' prefix: '@await'.
  3. Rename them. Goto "Situation 1". :)
Jan 7, 2015 at 11:59 AM
Edited Jan 7, 2015 at 12:13 PM
PauloMorgado wrote:
Imagine this method:
/* async removed as per your proposal */ Task<bool> GetBoolAsync()
{
    return await Task.Run(() => GetBool());
}
"Disabling" await would mean that the calling code would be calling something expected to be asynchronous (as the suffix announces it) that returns an awaiter (might be a Task or not). How would the compiler handle it?

How would the compiler change the behavior of Task.Run, or any other method in any other third party library?
Sorry, I couldn't catch what problem is here.
The Task.Run() returns a Task which is executed asynchronously (even in another thread).

The method marked as "async" is not guaranteed to be executed asyncronously. It is executed synchronously until the first "await". Moreover even after the first "await" it can continue executing synchronously (if the awaiter is already Complete). With no "awaits" it is executed synchronously.

If the method is not marked as "async" it isn't guaranteed that it doesn't run any asynchronous work.

So from the caller point of view the "async" keyword only works as a hint. But we already use the "Async" postfix for the method names for this purpose!
Jan 7, 2015 at 1:31 PM
Edited Jan 7, 2015 at 1:32 PM
artelk wrote:
Imagine you have a project which you are migrating to the new .net with the new compiler.

Situation 1: There are no classes/methords/fields/variables named "await" in the project (I believe this is true for more than 99.99% projects) => no problems with compilation. After migration you can add some methods returning Tasks (or other awaitables) which use the new "await" feature.

Situation 2: You use "await" as a name of something. You have the following options:
  1. Specify the compiler option disabling the "await" feature. After migration you can use the new "await" feature but you should add [EnableAwait(true)] for the methods or classes (that enables this for all their methods). To access the existing classes/methods/... named "await" from the contexts where the "await" feature is enabled you use '@' prefix: '@await'.
  2. Add [EnableAwait(false)] where you use the "await" classes/methods/... => After migration you can add some methods returning Tasks (or other awaitables) which use the new "await" feature. To access the existing classes/methods/... named "await" from the contexts where the "await" feature isn't disabled you use '@' prefix: '@await'.
  3. Rename them. Goto "Situation 1". :)
As is the case with pretty much every keyword added to C# since it's initial inception the await keyword is contextual, meaning that the compiler will only treat it as a keyword if it cannot legally treat it as anything else. So if you have an existing class/method/field/variable with the name await (or async) that code will happily continue to compile. The only time you would need to rename them or @-qualify them if you wanted to use them within the context of an asynchronous method or lambda.

This ship has long since sailed after this very argument arose many, many times. I wouldn't expect any traction to your suggestion.
Jan 7, 2015 at 2:10 PM
Edited Jan 7, 2015 at 2:12 PM
My opinion: at least the "async" keyword should be renamed to something like "enable_await" because marking the method by this keyword doesn't make the method asynchronous. That's why this name is inapropriate...
Jan 7, 2015 at 2:27 PM
artelk wrote:
PauloMorgado wrote:
Imagine this method:
/* async removed as per your proposal */ Task<bool> GetBoolAsync()
{
    return await Task.Run(() => GetBool());
}
"Disabling" await would mean that the calling code would be calling something expected to be asynchronous (as the suffix announces it) that returns an awaiter (might be a Task or not). How would the compiler handle it?

How would the compiler change the behavior of Task.Run, or any other method in any other third party library?
Sorry, I couldn't catch what problem is here.
The Task.Run() returns a Task which is executed asynchronously (even in another thread).

The method marked as "async" is not guaranteed to be executed asyncronously. It is executed synchronously until the first "await". Moreover even after the first "await" it can continue executing synchronously (if the awaiter is already Complete). With no "awaits" it is executed synchronously.

If the method is not marked as "async" it isn't guaranteed that it doesn't run any asynchronous work.

So from the caller point of view the "async" keyword only works as a hint. But we already use the "Async" postfix for the method names for this purpose!
What is the type of the expression after the return keyword, with and without await enabled?

And, what would happen with this code?
/* async removed as per your proposal */ Task GetBoolAsync()
{
    if (await Task.Run(() => GetBool()))
    {
        Console.WriteLine("true");
    }
    else
    {
        Console.WriteLine("false");
    }
}
Jan 7, 2015 at 2:49 PM
artelk wrote:
My opinion: at least the "async" keyword should be renamed to something like "enable_await" because marking the method by this keyword doesn't make the method asynchronous. That's why this name is inapropriate...
Do you really think this is so bad that two versions later needs to be fundamentally changed?

Do you really believe it will ever happen?
Jan 7, 2015 at 3:07 PM
PauloMorgado wrote:
What is the type of the expression after the return keyword, with and without await enabled?
With await: bool
Without await: Task<bool>

PauloMorgado wrote:
And, what would happen with this code?
/* async removed as per your proposal */ Task GetBoolAsync()
{
    if (await Task.Run(() => GetBool()))
    {
        Console.WriteLine("true");
    }
    else
    {
        Console.WriteLine("false");
    }
}
The same as if you add "async" and use current compiler.
Jan 7, 2015 at 4:07 PM
PauloMorgado wrote:
Do you really think this is so bad that two versions later needs to be fundamentally changed?
The changes are not so fundamental and are backward compatible. The existing code should be successfully compiled (but with warnings like "use enable_await instead of async").

But "fundamental" changes are expected in the documentation.
Currently the feature is named "async/await". It seems the name is choosen to be similar to "fork/join" or other "start/wait"-like patterns.
Renaming it to more accurate "enable_await/await" makes the absurd visible so it will be named just "await" after that, i suppose.
Do you really believe it will ever happen?
Yes, I'm quite naive today. :)
Jan 7, 2015 at 4:24 PM
artelk wrote:
PauloMorgado wrote:
What is the type of the expression after the return keyword, with and without await enabled?
With await: bool
Without await: Task<bool>

PauloMorgado wrote:
And, what would happen with this code?
/* async removed as per your proposal */ Task GetBoolAsync()
{
    if (await Task.Run(() => GetBool()))
    {
        Console.WriteLine("true");
    }
    else
    {
        Console.WriteLine("false");
    }
}
The same as if you add "async" and use current compiler.
I was asking what would the compiler do related to your enable/disable await proposal, not the removing of the async modifier.
Jan 7, 2015 at 4:28 PM
artelk wrote:
PauloMorgado wrote:
Do you really think this is so bad that two versions later needs to be fundamentally changed?
The changes are not so fundamental and are backward compatible. The existing code should be successfully compiled (but with warnings like "use enable_await instead of async").

But "fundamental" changes are expected in the documentation.
Currently the feature is named "async/await". It seems the name is choosen to be similar to "fork/join" or other "start/wait"-like patterns.
Renaming it to more accurate "enable_await/await" makes the absurd visible so it will be named just "await" after that, i suppose.
Do you really believe it will ever happen?
Yes, I'm quite naive today. :)
Adding a warning for something that was right is kind of a breaking change. Specially for those, like me, that treat warnings as errors.

Can you point to where in the documentation the feature is called "async/await"?

Regarding "enable_await", can you indicate any other C# keyword that uses underscores?
Jan 7, 2015 at 4:56 PM
Edited Jan 7, 2015 at 5:10 PM
PauloMorgado wrote:
I was asking what would the compiler do related to your enable/disable await proposal, not the removing of the async modifier.
By default the await is enabled and when compiler meets await in the code the method is converted to the state machine (similarly to yield return feature).
You can disable the await by a compiler option so await in the code will cause a compilation error "await is disabled, please enable it" (unless this is supposed to be a class/method... name).
In case the await is disabled by the compiler option you can explicitly enable it for a certain method or for all methods of a certain class by adding the [EnableAwait(true)] to the method or class respectively.

Or I still don't understand your point?
Jan 7, 2015 at 5:09 PM
Edited Jan 7, 2015 at 5:12 PM
PauloMorgado wrote:
Adding a warning for something that was right is kind of a breaking change. Specially for those, like me, that treat warnings as errors.
Possible solution: there could be an option to suppress this warnings. When VS converts the projects from the old .Net it could be asked whether to suppress them or not.
Can you point to where in the documentation the feature is called "async/await"?
For example: http://msdn.microsoft.com/en-us/magazine/dn802603.aspx
Regarding "enable_await", can you indicate any other C# keyword that uses underscores?
I cannot indicate. Is this the only cause why it is named "async"? ;)
It could be named e.g. "awaiting" or something like that.
Jan 7, 2015 at 5:14 PM
artelk wrote:
PauloMorgado wrote:
I was asking what would the compiler do related to your enable/disable await proposal, not the removing of the async modifier.
By default the await is enabled and when compiler meets await in the code the method is converted to the state machine (similarly to yield return feature).
You can disable the await by a compiler option so await in the code will cause a compilation error "await is disabled, please enable it" (unless this is supposed to be a class/method... name).
In case the await is disabled by the compiler option you can explicitly enable it for a certain method or for all methods of a certain class by adding the [EnableAwait(true)] to the method or class respectively.

Or I still don't understand your point?
No. It's definitely I that don't understand a thing of what you're proposing.
Jan 7, 2015 at 5:25 PM
artelk wrote:
PauloMorgado wrote:
Adding a warning for something that was right is kind of a breaking change. Specially for those, like me, that treat warnings as errors.
Possible solution: there could be an option to suppress this warnings. When VS converts the projects from the old .Net it could be asked whether to suppress them or not.
Can you point to where in the documentation the feature is called "async/await"?
For example: http://msdn.microsoft.com/en-us/magazine/dn802603.aspx
As much as I consider Stephen Clearly an authority in "async/await" (short for async functions (§10.15 in the C# Language Specification) and await expressions (§7.7.7 in the C# Language Specification)) (probably the number one authority outside the TPL and language teams), I don't consider one magazine article documentation. Sorry.

artelk wrote:
Regarding "enable_await", can you indicate any other C# keyword that uses underscores?
I cannot indicate. Is this the only cause why it is named "async"? ;)
It could be named e.g. "awaiting" or something like that.
Looks to me that you have a problem specifically with the async keyword itself and not the fact that one is needed. Apparently any other keyword is good enough for you, even if it doesn't even look like a C# keyword.
Coordinator
Jan 25, 2015 at 5:03 PM
artelk wrote:
My opinion: at least the "async" keyword should be renamed to something like "enable_await" because marking the method by this keyword doesn't make the method asynchronous. That's why this name is inapropriate... So this is misleading even at the feature naming level. It seems they've made this intentionally misleading to make asynchronous programming a bit more difficult. ;) The olny reason for the "async" keyword I know is "what if sombody use 'await' as a class\method
We didn't pick this design to be intentionally misleading :) And actually, the ability to remain back-compatible with code that used "await" as an identifier was only part of the reason.

Artelk, in your own words, you say "marking the method by this keyword doesn't make the method asynchronous". So in your own opinion, what exactly is an asynchronous method? (This isn't a rhetorical question... I am interested to hear what you say).

At the point where we started the feature, most of the world used the word "asynchronous" in different ways, and in ways that were usually unhelpful and/or incorrect. Probably the commonest belief was that "async means that the method runs on a different thread". We knew that we were introducing a new concept, a new kind of method and a new kind of control flow. We wanted to give it a name, and we wanted everyone to refer to it by that name. "Async" was the best choice of word, and a technically correct and good use of the word. By sticking the modifier there in the method, we've made the entire .NET community refer to it by this name, standardizing everyone on the same way to talk about the thing, and we're helping the world's wider community of computer programmers by reclaiming the best meaning of the word.

Also, we specifically designed the learning process where people stick the word "async" on a method in the mistaken belief that this would cause the method to run on a background thread. But then the compiler gives a warning message "this method has no awaits and so will complete synchronously". This I think was a clever way to introduce people to the issue, one that many wouldn't have noticed or understood otherwise.

Also, as has been said, I think it'd be ugly for the meaning of the return statement to change based merely on whether there happened to be an await somewhere else in the code. I know that iterators work that way, and it's ugly, and if we could do them over them we would.
Jan 27, 2015 at 6:04 AM
lwischik wrote:
We didn't pick this design to be intentionally misleading :) And actually, the ability to remain back-compatible with code that used "await" as an identifier was only part of the reason.

Artelk, in your own words, you say "marking the method by this keyword doesn't make the method asynchronous". So in your own opinion, what exactly is an asynchronous method? (This isn't a rhetorical question... I am interested to hear what you say).
Asynchronous means "don't block the caller". This can be done by
  1. Starting a new thread and executing the code from there
  2. Add "work item" to ThreadPool and execute the code on its thread
  3. Post delegate to message loop (WinForms&WPF)
  4. IOCP magic
  5. Some other magic (coroutines etc.)
  6. Combination of the above. E.g. use IOCP for IO operation then continue with some calculation on a thread in the ThreadPool and then continue on UI thread (post the continuation delegate to message loop).
lwischik wrote:
Also, we specifically designed the learning process where people stick the word "async" on a method in the mistaken belief that this would cause the method to run on a background thread. But then the compiler gives a warning message "this method has no awaits and so will complete synchronously". This I think was a clever way to introduce people to the issue, one that many wouldn't have noticed or understood otherwise.
I've seen someone added something like "await Task.Delay(1)" at the end of the method hoping this made the full method code to execute asynchronously. :)
The "someone" is native English speaker so that warning message is still a bit ambiguous, I suspect. Is it?