DEV Community

Discussion on: How C# asynchronous programming is different than what you think

Collapse
vekzdran profile image
Vedran Mandić

A very detailed comment, thank you for the overview of other languages!

There is definitely huge effort behind the language and framework design as you state, and it resulted in such a concise and succinct interface. And yes the thread pool is included in the whole problematic, and the key is not to exhaust it with writing bad async/await.

So, how I see it, async/await is about performing non blocking actions by the help of Tasks (a wrapper on top of Thread) and returning those who would block to the pool to free it for more processing and doing all of that elegantly in code so the reader and reviewer can mentally map the process as the code is read/compiled. And that is about it. If you need parallelism then favor Parallel and other libraries. What I found most in my short experience as a C# developer is that people do not understand what you have excellently pointed out - blocking and yielding control back, that's why often I see code that ignores proper context switching.

Luckily, the newest manifestation of .NET, the .NET Core, has removed the sync context in their ASP.NET web platform, which again now takes more education for developers as people are very puzzled when to use .ConfigureAwait(false) (you don't have to as S. Cleary points out in this article in details, but still one has to take care now of other problems due to implicit parallelism which might occur as a non-easy to observe side effect) and other performance optimization solutions.

By the way I dislike the similarity of JavaScript syntax as it basically covers up a ton of generator + Promise (a better callback actually) code, but still kudos to code-readability which is must these days. :-)

Collapse
rhymes profile image
rhymes

Hi @vekzdran thanks for the feedback! It took me a while to write the comment haha. I'm thinking I should "upgrade" it to an article.

There is definitely huge effort behind the language and framework design as you state, and it resulted in such a concise and succinct interface. And yes the thread pool is included in the whole problematic, and the key is not to exhaust it with writing bad async/await.

As every technical decision there are advantages and drawbacks. I applaud their decision though, they created a concise interface as you say on top of two complicated mechanisms: asynchronicity and thread pooling.

The clear advantage is that you can utilize more resources of the hosting machine and in the future optimize the pool as you see fit. Maybe they can even in a future version allow the user to inject a "single threaded event handler" for people who don't want to use multiple threads under the hood, but I'm in fantasy land now :D

What I found most in my short experience as a C# developer is that people do not understand what you have excellently pointed out - blocking and yielding control back, that's why often I see code that ignores proper context switching.

That's normal, I think it's because most people learn a form of sequential imperative programming which tends to be blocking. You do A, then B, then C and then you compute the result. That's it. All concurrency model require us programmers to think of what could happen outside of the "main flow".

From the article you linked I read this:

However, you shouldn’t. Because the moment you block on asynchronous code, you’re giving up every benefit of asynchronous code in the first place. The enhanced scalability of asynchronous handlers is nullified as soon as you block a thread.

That's it. If you block too much, the thread is not released to the pool and you slow everything down.

but still one has to take care now of other problems due to implicit parallelism which might occur as a non-easy to observe side effect

Yes, I read the part in the article with the example with a List<string>. The fact that threads are hidden to the user, does not mean all the usual problems magically disappear. I wonder if Rust really nailed it and answered all of these problems with their concept of borrowing. I don't have experience with it, I've read a few articles and in a situation like that the write at the same time (thus losing one of the elements of the list) shouldn't happen because only one thread can have the list reference at any time and that's enforced by their compiler.