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. :)
@Mani, as I have stated below, the infestation concept is not local to this idea. Here is an article on MSDN that acknowledges this very quality:
Search for zombie or turles. There is definitely a "spread" or "infection" of the code that follows integration of this API. While you are correct to state that the compiler requires these words, the *idea* again (this being an idea/suggestion site) is to improve this API so that these words are not needed, thereby returning cleaner code/design to our solutions.
Additionally, the guidance you provide for calling ".Wait()" while intuitive is actually one of the gotchas for using this API:
Mani Gandham commented
The async/await in C# is one of the best implementations of asynchronous abilities in a programming language.
This is fundamentally how it works. There is no "infestation" because the async keywords are necessary for the compiler to build the state machinery around the method call. The Task return types are necessary to pass the necessary async state information along with the actual result of the method. These are required and there's no way around it, outside of magical constructs that would only further confuse programmers.
You can stop the async keyword usage at any level too by simply using the TPL or the Task methods. Just get a task handle for an async method and .Wait() for it.
Some more pitfalls and explaining away of TaskAPI here:
Async gotchas are now making their way into MSFT code, making a clinic on how not to write async code. The horror:
API's getting bit by the void async oddity:
Yes, we can do better. :) :) :)
I appreciate your zeal to preserve the status quo (or rather, unnovation) here, but I think it's safe to say that if users are comparing your API to a zombie infestation, that there is room for improvement. :P This is not my term, either. This is a well-used term you can see in both blogs and StackOverflow, which of course is quite apt.
With that said, you're thinking with current restrictions and designs, while the point of the idea is to improve upon them with further innovation, perhaps (and most likely) with new language keywords and syntactic sugar.
I agree this a non-trivial ask, and will involve many IQ cycles that unfortunately are outside of my scope and experience here. All I (and others know) is that I/we had pretty, consistent code, interfaces, and design before TPL, and now with post-TPL they are gone, with a whole lot of zombie horde to double-tap in its wake.
Joseph N. Musser II commented
For one thing, I don't think it really is an infestation. I've been in a good variety of codebases that use it and it has rarely been laborious. But I know what you're referring to, so let's just go with that. How would you remove it? You would have to go either 100% sync or 100% async or invent a way that the same code could run both ways. You can't run synchronous methods asynchronously without losing all benefits of async and you can't run asynchronous methods synchronously without losing all benefits of sync.
Each scenario is specialized. Say you're reading from I/O. If the code runs synchronously, you make the system call to read. If it runs asynchronously, you make a system call to be notified when the system is done reading and then call back to a thread pool thread or to a UI message pump. Those two are nothing like each other but each has distinct benefits that we need to keep around. You have to keep two distinct methods around for this because sometimes you need one and sometimes the other. Does that make sense?
Async is inherently expensive just because it's the ideal solution to an inherently expensive problem. You want to make it very clear when you are and aren't using it. Moving to a world where it's all the same method and same return type for extremely different processes would be frustrating.
@Joseph, You bet I'm complaining. :) Complaining with hopes of improving, as per the title of the vote.
I'm afraid you're going to have to work a little more to convince me how being infested with zombies is *ideal*. Sounds like were once a human coder and have been bitten and overrun by the horde. :P
If you mean in the current scope of capability, that is one thing, but that is not the ask here. The ask is to remove the zombie infestation altogether, along with the inordinate esoteric requirements to work with the current API, of which I am sure you are painfully familiar with, or at least were at some point.
We can do better, I KNOW WE CAN!!! :)
Joseph N. Musser II commented
I'm not really sure this is actionable or even desirable. The "zombie" infection and having *Async versions of methods and async void are actually the *ideal* outcome as far as I can tell. There is no helpful way to generalize async and non-async code that I have been able to find. Without async void, how would you be able to write async event handlers in any practical way? I could go on.
Unless you have some ground-breaking theory proposals to point to, it sounds like you're mostly complaining about the fundamental nature of async programming rather than complaining about C#'s solution in particular. C#'s async/await is quite the state of the art right now when it comes to solving the callback **** problem, so perfectly executed that other languages are copying it.
On the cutting edge there are exciting improvements like custom builders for custom Task-like types, but for the most part the dichotomy between synchronous and asynchronous (callback-driven) code is a practical and useful one. Certainly, I may stand corrected on this and that would be exciting, but we'll need to be pointed to some heavy-duty research rather than complaints. No offense!
This looks like a vote/issue that will address some of the notorious TPL friction. Please upvote it here:
Cool, thanks for the share, Qwertie. It's got my votes. :) To be sure, this vote is asking for improving TPL as a whole, in any way necessary/possible. If that involves stack switching (or really, ANYTHING to defeat the zombies and esoteric usage friction), then so be it. :)
The alternative to the async/await feature (to avoids the async "infection" and mandate to use `Task`) is stack switching. .NET is unlikely to get this feature (especially since async/await already exists) but the uservoice item that seems to represent stack switching has more votes than this item:
SO CONFUSING!!! ;)
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.