Why even asynchronous programming?
Non-blocking code... period!
That's the whole goal of async code, you wanna write an app that doesn'...
For further actions, you may consider blocking this person and/or reporting abuse
Hi Yaser, thanks for the explanation! My reply is going to be long and will take a few detours, so brace yourself :D
Async programming as programming model has been around for quite a while. Think about how I/O works with interrupt handlers in operating systems, or that most desktop UI frameworks (for example the age old Win32 for example with PostMessage) have an event queue. Think about epoll in Linux, kqueue on BSD and MacOS and Windows's I/O completion ports.
Async as model can be implemented with different techniques.
Recently, as you explained, a lot of languages are adopting these two "magical" keywords:
async
andawait
but the underlying concepts definitely predates the "shortcut" keywords.A quick trip down memory lane: I remember reading about the reactor pattern in the context of Twisted, a Python framework (arguably one of the most famous at its peak in the realm of high level languages) where everything is async. It was built to be multi platform on top various I/O sys calls, it introduced people to the concept of deferred (basically the same thing as a promise or a future), it allowed to branch out using threads if needed (but with the same interface, hence the abstraction mentioned earlier) and it even supported GUI event loops so that you could integrate with other toolkits. It had widespread usage for a while: Apple famously used it for the calendar server, people built reservation systems and file servers and many other things. It came with support for many, many protocols. Rubyists a few years later launched EventMachine which clearly was inspired by the work of Twisted and other frameworks like it (I think Boost.ASIO for C++ at least).
Why these frameworks didn't take over everything for ever and ever? I don't know the exact reasons but I can make a few guesses. I used Twisted a lot back in the day and the first complain I had (most of people unfamiliar with it did) was that it forced everything to be async. You know very well, even for modern implementation of this model, that you can't magically intermix blocking stuff so once you go async, you more or less have to go async all the way. There was also way less information back then (I remember reading Twisted's own source code to figure out how to use some parts of it or literally write to a friend of mine who understood it better than I did and also was a contributor to the project). Twisted and EventMachine are the embodiment of the concept of a framework: they call your code and your code has to be ready to be called and to not block the main event loop. A lot of existing code wasn't async, web servers were either multithreaded or multiprocess (!!) and even though they had the usual drawbacks of those concurrency models, people valued their own developer experience over the idea of having to rewrite stuff I guess, even though Twisted performs very well. I'm sure there are other reasons but this didn't probably help adoption, which is probably why the two most famous web frameworks in Python were not built around an async model.
What does it mean not to block? It means that it needs to yield back control (
yield
is also an ever present keyword in Python's cooperative multitasking but that's another story) to the caller pretty quick because the framework has to pass the token between all the participants. If one takes the slot and decides to go on a rampage without giving back the control, everything blocks and suddenly no one can do anything, hence why all the I/O libraries have to be rewritten using the async model when you integrate async. You can't one one that yields back control and one that doesn't. This is both an advantage and a disadvantage and people have been debating on this since the beginning. This is Twisted's implementation of async, and Node's as well as far as I know. Though we can't say Node suffers by adoption problems, it clearly worked because for a combination of factors (inherent merits, timing, marketing and having chosen a language millions of people were already familiar with). So well, you never know, Node clearly showed there's a market for a "single threaded event loop" type of ecosystem.Nginx must have indirectly helped changing people's perception of async in web servers. No more "difficult to debug" threads, no more multiprocessing that doesn't scale with heavy load anyway.
So, coming back to today. Python and JavaScript added async/await to their core, other languages have some sort of async support or are about to and C# seems to be well equipped too.
As I said, a drawback (at least in my opinion) of the async programming model is its all or nothing contract. Think about JavaScript, the second you put "async" on a function, every caller has to be declared async. So it becomes a literal tree structure of async invocations, isn't this the same thing as a framework repackaged with a nicer interface? You are marking your unit of works with a special keyword that the platform knows how to treat differently.
To be clear: I think it's really nice that the developer experience now is better: no more rewriting the whole code to fit a third party framework, no more wrapping results in promises/futures in most cases, no more calling the reactor manually or yielding results.
Going back your article:
That magic scares me sometimes ;)
I don't know if I agree with the C# developer you quoted because I like options. I like the option of choosing to program in an async model or not. I'm not even sure it's the best model, heck, I'm not even sure a best model exists. That's why I like options :D
Correct me if I'm mistaken because I only had a quick look at the .NET async patterns you linked but it seems like three things pop up: async is implemented on top of a thread pools, the interface is really well thought of and .NET designers have gone all in. So yeah, it's widely adopted but not by chance, it's because people have put a lot of thought in this and planned it well.
Again, I understood correctly, the fact that it is implemented transparently on top of threads means that it can potentially use multiple cores (though it's not guaranteed), which seems a smart move since practically all the CPUs are multicore now.
So yeah, .NET async model is very interesting :-)
Sorry for the super long reply but it's a topic I'm clearly interested in and I like to poke holes into. I'm quite convinced that most if not all concurrency models suck, probably because any type of concurrent execution is hard.
ps. I wouldn't use the expression "C# dudes" because it probably doesn't represent the full spectrum of people working on C# and .NET ;-)
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. :-)
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.
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
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:
That's it. If you block too much, the thread is not released to the pool and you slow everything down.
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.I agree with you, async-ness has been there for long in different flavors.
And as you said regarding Twisted, EDA (event driven architecture) is never pleasant, and it's hard to predict and even harder to test!
I also really believe that simpler techs like Node which gives the developers easier way to achieve good-enough products (for tens of thousands of users) made the adoption easier rather than all-async-hard-to-understand techs.
async/await uses no threads, you can imagine it as a call-back. (this is a more detailed explanation)
Since no threading is used with async/await, it's mainly meant for IO-bound operations (threads still have their place in CPU intensive operations.)
Fun story:
We built once a simple registration sub-system that sends a verification email to users once they register, very simple and straight forward with SMTP.
And that time we used the magic of C# async/await, it was good... but in some rare cases, the mails aren't sent simply cuz that's how SMTP works (it might drop the connection time to time with more requests coming in).
We decided to switch into Django/Python cuz it has tons of 3rd part libs that serve our goals really well. So it means we will lose the magic of C# async/await!
Of course more mails were dropping after the switch, until we thought about using Amazon SQS to keep the request of sending the mail in the queues and never allow it to pass from the queue till it's successfully sent... guess what happened? we got a heck more reliable and faster mail sending, and I've never heard any user complaining about his verification email after!
are you sure? I'd agree normally but in this article I found on MSDN: Task-based asynchronous pattern (TAP) and Async in depth linked from Async overview they clearly talk about async tasks mapped on a thread pool.
@vekzdran also linked article ASP.NET Core SynchronizationContext which talks about thread pooling again.
But probably we're talking about two slightly different things due to my lack of familiarity with .NET and C#.
async/await by themselves don't introduce threads, but ASP.NET uses async on top of a pool to handle concurrent requests? Is that the source of my confusion?
I read this in the article you linked:
I'm a little confused by the paragraph you linked. What this paragraph says to me is that calling
await something
does not generate a thread (which makes sense, otherwise every call would create its own thread and add a lot of context switching). But it doesn't exclude the fact that an async operation MIGHT be running on a different thread than the one it was called from (which is compatible with the idea of pooling, see what happens in Go with goroutines which are transparently mapped on a pool of threads). As it says, if you have a CPU intensive operation (perhaps blocking the context switch of the continuations) you can explicitly tell the compiler to move the operation to a background thread. So yeah, from what I've gathered by this and the other articles thread pooling MIGHT be involved or not, it's just not visible to the programmer (which is why it's a great implementation).Yeah, putting a queue between you and the SMTP server was a good idea :)
You might want to follow Christine Spang if you're still interested in all things "Python + email":
Christine Spang
My rule of thumb is:
If I want nonblocking code that is related to IO (say talk to db) without creating a thread, I simply throw async/await everywhere.
If I want to create a thread to do some cpu heavy computation, I add Task.Run and fill in the lambda my sync code (and for sure that requires adding async/await).
This is the way I do async C#... I should say I never tried to check those statements 😁
Also, some operations might lead the OS to create a thread, but that doesn't count on our app process (it's the way the OS does its business instead).
Thanks for the recommendation, I just read her posts and they seem interesting!
This is a cool example on how async/await really helps (you had to go into a message queue service instead of wrangling async await, woah, hope you solved that somehow). You did fix it but introducing so much infrastructure, is that right? I mean, you solved the issue that's what counts, but is it worth? Thanks.
That infrastructure is really cheap, we thought about adding redis or rappit MQ, but we made our minds with SQS cuz it's 10x cheaper (cuz we didn't wanna change our server plan to add more memory, and not mentioning ensuring the MQ runs with no problems).
For the technical cost, it took us about 3 days to add celery in front of the mail sender and deploy it on production (after deploying and trying it on staging).
I know took a bit of time and effort to get things done, even we had already the resend verification button... but we kept getting moaning from our beloved users 😄
So, for stopping the users' moaning, I would say it's totally worth it!
I do like the way async/await works in C#, it only felt like a burden when my entire app was synchronous but once I started moving to async, it became easier and easier.
With some of the examples in your post though, I would be careful with how they use async. Things like
async void
, returning a task without awaiting, and callingtask.Result
can have different issues in different circumstances.Here is a good guide of when and how to use async/await which covers how/why some of those aspects can be bad: github.com/davidfowl/AspNetCoreDia...
That guide might be aimed at ASP.Net Core however many (maybe all) aspects are still relevant outside of it.
Thanks for letting me know... for the sake of completing the article I didn't look carefully into the codes, I just copied and pasted couple of examples from various places and mentioned their sources; I will update the examples soon.
No problem 🙂
Some of these things about async/await aren't all that obvious and different understanding of how to use it likely changed over time. The people that probably wrote the example code probably had it based on old information.
One of the things that gets me still with the async/await pattern is whether I need to call
ConfigureAwait(false)
or not - there seems to be a lot of different opinions about it online from a variety of people smarter than me.Yeah, I still remember the first days I was cluelessly reading different articles about async C# and mixing new code with old one :D
C# is moving a bit fast that I feel sometimes it's burdensome to catch up with their latest updates.
I feel the same way about JavaScript!
As much as some of the new features that are being released (in JS, C#, CSS etc) are helpful and solves certain problems, there are some cases where I just look at it and wonder how we got here as it doesn't look easier/better than what existed.
JavaScript is a whole different story 😆
They just add all sort of keywords and features into the language just to feel "Oh, JavaScript can do that too" 😑
For imperative style
async
is probably the best. There is no practical way or need to abstract to higher-kind, so it is necessary and sufficient to have keywords for a few managed effect and make special case type checks for them.For purely functional style asynchronicity is just another monad in the effect stack. More specifically, the standard
Concurrently
is not even a monad, but a monoid, so you can only express parallel composition, and any dependency (blocking) has to be lifted and chained to the innerIO
. The next step in this is streaming libraries and the functional reactive style.Another famous async model is Erlang's actor model.
Yaser this is a wonderful summary, thank you sincerely! It will help me a lot in my future presentation in which I plan to point all common pitfalls in usage of async and await.