Improve Asynchronous Programming Model
The asynchronous programming model introduced in .NET with the async/await keywords was a step in the right direction and a great improvement over previous design, but there are a few fundamental and disruptive qualities to it that need addressing:
1) Zombie-code. This is the biggest issue with .NET asynchronous programming model currently. Once asynchronous code finds its way into your code base, all code referencing it (and being called from it) must also start to incorporate asynchronous keywords and functionality. This results in a phenomenon which has been described as "Async Zombie Infection." It would obviously be great/ideal not to have a feature associated with zombies running around and infecting your code. :)
2) Coupling to Task and Task<T>. Code is now coupled to the Task and Task<T> objects. Interfaces and classes now have to change their signatures (and behaviors) to reflect these objects, leading to added complexity for a code base. Ideally, an asynchronous design should not be coupled to any class, and consumers should not have to change their consuming classes to enable it.
3) Contract pollution. When a class implements the current asynchronous model, it now introduces an asynchronous method for each and every synchronous method. This leads to longer intellisense lists and search times for finding a method, as now a class signature is twice as long. This also leads to a more complicated feeling to a class and code base. It would be better to have a design where a GetAsynchronous() (extension?) method is called to retrieve an object's asynchronous functionality.
4) Code Pollution. As alluded to the first two items, code that uses the current asynchronous model now has to incorporate not only task and Task<T>, but now have to incorporate "async" and "await" throughout their code bases (more and more as the zombie infection spreads), as well as incorporate naming conventions that add "Async" to the end of classes and method names. This leads to what feels like code pollution and messy code.
For instance, take the following interface;
public interface IHelloWorld
string SayHello( string message );
The asynchronous version is:
public interface IHelloWorldAsync
Task<string> SayHelloAsync( string message );
The asynchronous version has 16 additional characters now over what used to be, due to coupling and convention. This might not seem much for a simple example, but when multiplied out in a code base that has hundreds of classes and thousands of methods, it adds up.
(A parallel to this are class properties. In C#, properties used to be very verbose leading to a LOT of code in a class file, but now this has been adjusted to be extremely terse, especially 6.0. Perfection!)
5) The "gotchas" ... there are a lot of these. The biggest primarily being the "async void" problem and the exceptions that occur from this. This feels like a lot of friction and is indicative of further design improvement.
6) "async" is not a word. This encourages laziness and sloppiness in design and overall approach. That is all. :)
A solid example of an asynchronous "gotcha": https://ayende.com/blog/173057/production-postmorterm-houston-we-have-a-problem
Another thought here to add to the list above:
7) Debugging. The experience of debugging an asynchronous call stack is terrible. You have to wade your way through several frames to one that can load, and those frames that do load are loaded with all sorts of compiler symbols and ugliness. This simply is not the case with a synchronous programming scenario.
Developers Win! commented
If anyone is interested, this vote and others like it that aim for key improvements to Visual Studio are being tracked on Developers Win! The first report can be found here:
Future weekly reports will be found here:
As of today, the Visual Studio Improvements category (where this vote is tracked) has 223 combined points. This report will be useful in seeing how many votes occurred over what period of time. Please continue sharing this idea and make sure that our voices are heard!
Thank you John for your feedback on my feedback. :)
To start, I didn't make it clear, but the issues that i outlined are in descending order of priority. The naming convention is not as bad of an issue as say, zombie code infestation. With that said, I do not hold exceptions for words such as "extern", "enum" and "const." Those are not words either and should be extended to read as full words (IMO). The difference between those words and "async" is that writing "const" or "extern" in your code somewhere does not beget more of those words. Try doing a search for the amount of times "async" shows up in a code base designed around the current asynchronous model and compare it to the amount of times "const' and "extern" are used. I would also like to point out that UI and IO are acronyms, and not non-words.
Additionally, I do not like typing out "asynchronous," just as much as I do not like to type out "async". I am suggesting that there is a cleaner way all around to introduce asynchronous code into a code base without having to denote it with such keywords altogether.
Secondly, I would like to reiterate my statement in the original post that the current asynchronous model is pretty cool, and a welcomed improvement over the prior model. I am in agreement with you. However, just as class properties used to involve a required backing field, and were ultimately reduced to a single, powerful line as an automatic property (complete with constructor support) in C#6.0, there is always (ALWAYS!) room for improving a model to make it even better. That is the purpose of this vote.
I would prefer a design that keeps my synchronous (cleaner) interfaces and classes in tact while still being able to denote a body of code is meant for asynchronous calls without having to couple it to specific classes.
WCF design comes to mind. I can use a regular interface and still have its implementation dive into the WCF guts if I would like, via the use of OperationContext and other classes. It would be great to expose TaskContext (or AsynchronousContext) to do something similar with the .NET asynchronous model.
The AOP technology PostSharp also has some wicked asynchronous capabilities used via attributes. That is also another alternative design that can be used, that I would find cleaner.
That way, all code is kept synchronously-designed (and the integrity of the original design kept intact), but can further be used in an asynchronous context.
And obviously, the goal here is to keep the current asynchronous model in tact, so that those who prefer the zombie infection can continue their plague-ish code if they so wish. :P When we talk about making a "major change" I am not saying we should forego the current model altogether, but again (just like properties to automatic properties), keep the existing model but also provide an improved way that allows it to be even better.
Finally, I do not think it is professionals that "let go of" design that could stand to be improved. On the contrary, professionals constantly aim and find better ways to program and design. That is what this board is for, to highlight features that require improvement, and find ways of making them better.
John Tasler commented
"const" and "extern" are not complete words either, yet are long-accepted as language keywords. Also, some types have incomplete names, because they are well-known. Examples are Enum, IO, UI. Have these keywords and symbol names ever caused an uproar in the C#, C++, or C language community? Plus, you're already concerned about the extra keystrokes involved with the Async suffix, so it seems odd that you want to type out the keyword as "asynchronous" with 7 extra characters. This concern seems to be only an aesthetic disagreement, that professional developers quickly learn to let go of. I'm grateful for open community design, but such a major change to a language should be made with the utmost concern of value vs. risk vs. cost.
For the concern of dependency on Task/Task<T>, what would be the scenario where this causes a problem? .NET Core includes these Task types, so cross-platform issues should not be a concern. Also, the "foreach" and "yield" language keywords are entirely dependent upon the IDisposable, IEnumerable, and IEnumerator interface types. I'm glad they are!
In "green-field" code bases, the async/await/Task/Task<T>/IAsyncOperation facilities are a huge win over bug-prone threading synchronicity issues. It doesn't alleviate them all. Of course I still need to occasionally use the synchronization primitives, such as the Monitor class (via the "lock" language statement), but at least the async/await features require much less, and much less complex and redundant, scoping code (try/catch/finally). Also welcome is the naming "convention" of the Async method suffix.
For "brown-field" projects (extending legacy code), use the new language features only as appropriate. Task and Task<T> have existed in the framework since .NET 4.0, so projects have been able to use them for many years. Using "await" on a Task is a powerful and game-changing alternative to manually calling the Wait, ContinueWith, etc. methods on a Task. But, again, no one is forcing you to use these in legacy code.
Although async/await had an ever-so-slight learning curve, the design is brilliant, IMO.