Cooking with async in mind
This summer is very busy at work. The Summer of Craft has been started to strengthen our development culture. Each day there is something interesting going on, where people can learn new things, share knowledge or meet with peers. One of the event is called Techfast, which is a 30 minute chat on a given topic over breakfast and I had an opportunity to drive one about asynchronous programming and execution. After the discussion, I received a feedback that it helped to understand what async is about, so I thought it would be worth to share it here as well.
Asynchronous programming is not a new concept. Asynchronous programming patterns exist in .NET in various forms since version 2.0, however with language support of async / await keywords in .NET 4.5 asynchronous programming became very popular and it is used now almost everywhere.
Now I’m using async features at work as well as my projects (LightBDD 2.0 is based on async) I still remember how confusing it was when I started exploring it. I am coming from C++ / C# background where in the past, if I wanted to implement some functionality to be more responsive or to process things faster, I was using threads. So when started working with async methods I had questions like:
- How does async method work?
- Does async execution involve multi-threading?
- How async is different from it?
After spending more time digging into the implementation details, reading documentation as well as simply using it for a while (which involved finding a few surprising behaviors) I got a better understanding of how it works. I realized however that it would be much simpler for me to grasp it by finding a simple real life example describing it well – that is why I initiated that discussion on Techfast.
And now let’s make some burgers
Let’s forget about all the programming languages and syntax and think for a while about something much nicer – food – to be more precise preparing food.
We would be preparing a burger from: buns, beef patties, onions, lettuce, tomato, cheese slices and ketchup.
Preparing a burger:
- First, wash all the vegetables, peel off onions, slice onions and tomatoes and finally put them on grill.
- Then put beef patty on a grill, remember to flip it a few times in order to make it well done (I don’t like blood in my food) and when it is almost ready, put a slice of cheese on top of it to melt a little bit.
- In the meantime, put buns on grill for a moment to toast them.
- When everything is ready take it off the grill, put meat in bun followed by onion, lettuce and tomato, add some ketchup on top and cover with other part of bun.
- The burger is done!
So what does it have with async? Let’s think for a while. I had a few distinguish tasks there:
- Wash, slice vegetables and grill them;
- Grill beef patty, flipping it few times and melting cheese at the end;
- Toast buns;
- Put everything together to finish burger.
Asynchronous vs synchronous operations
Now, did I do those tasks one after another? Did I wait till vegetables roast before I put patty on a grill? No not really. As soon as I put onions and tomatoes on the grill I started grilling the patty as well as buns. I have done it asynchronously! As soon as I realized that I will have to wait to finish my current task (grilling veggies) I switched to another one (grilling patty) then another (toasting buns). I could do that because I was not involved in the process of the grilling – it did not matter if I would stand next to the grill or not, all the ingredients will be roasting.
So what is the difference between synchronous and asynchronous operation? The synchronous operation is the one where I am fully involved. The example here is washing and slicing vegetables. I am actively doing it. I cannot walk of the sink or table hoping that vegetables wash and slice themselves. Also as I am actively doing that work, there is no waiting element here so no reason to start other task. I do it from the beginning to the end – synchronously.
So the first observation is: async is about utilizing time to do other task where in other case we will have to wait (do
Semaphore.Wait() methods ring a bell?)
Second observation: async is about dividing the operation into a set of smaller tasks that then could be executed asynchronously.
Does async mean multi-threading?
So how about multi-threading? Does async operations involve multiple threads?
Let’s modify our example a bit:
This time we would be making the same burger(s) but there would be two people preparing it.
The first cook takes the first available task (washing and slicing vegetables).
At the same time the other cook goes to the BBQ and start grilling the patty and buns.
When vegetables are sliced the first cook puts them on the grill as well…
We could continue this story with talking that cooks will move on to prepare other burgers or we could add more tasks for cooks, but I think the example describes the clue of the story.
The cook represents a thread. In first scenario we have managed to prepare a burger with one thread / cook where in second scenario tasks were distributed between multiple cooks.
As in both scenarios we managed to make a burger the conclusion is: async execution is independent from multi-threading. Multiple threads can support / speedup the asynchronous operations but single thread is enough to perform async operations as well.
When we cook, we have to perform various small tasks to finish with our favorite burger in a hand. Some of the tasks we do require our full involvement and attention such as slicing or washing vegetables. Other tasks however (such us grilling patties or roasting buns) does not require our full attention allowing us to start them, move on to something else in the meantime and come back to finish them when ready.
In the programming world it is very similar. Some of the operations such as typical collection sorting algorithms can be executed immediately, while others such as network or disk I/O operations requires executing thread to wait in order to finish.
The async programming with async / await model allows to build methods in a way that they are composed of tasks where executing thread can move on to execute different task if current task does not allow further processing without waiting.
As one cook is enough to make a burger, one thread is also sufficient for asynchronous execution. Similarly to more cooks, more threads may make things faster, but they are not necessary – for example:
- .NET console applications are asynchronous and multi-threaded by default,
- .NET WinForms applications uses one thread by default to process async methods called from UI thread.