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. :)
WE CAN DO BETTER!!! I KNOW WE CAN!!! :) :) :)
"From these results, we see that there is a real and significant overhead to using tasks. In situations with very low cost operations and extreme throughput requirements, it makes sense to avoid tasks."
And above all... it's SLOW! How slow, you ask? THIS SLOW:
Another solid article demonstrating some more gotchas. You know what, instead of waiting for this issue to be addressed, just follow this blog in the meantime. :P
@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.