Async/Await VS Threads

Async/Await vs Threads

can it completely replace the old way of using Threads ?

No. A thread can do many more useful things. Await is specifically designed to deal with something taking time, most typically an I/O request. Which traditionally was done with a callback when the I/O request was complete. Writing code that relies on these callbacks is quite difficult, await greatly simplifies it.

capable of doing what ever a Thread can do asynchronously ?

Roughly. Await just takes care of dealing with the delay, it doesn't otherwise do anything that a thread does. The await expression, what's at the right of the await keyword, is what gets the job done. Ideally it doesn't use a thread at all, it posts a driver request and once the driver completes the data transfer it generates a completion notification callback. Networking is by far the most common usage, latencies of hundreds of milliseconds are common and an inevitable side-effect of services moving from the desktop or a LAN into "the cloud". Using such services synchronously would make a UI quite unresponsive.

only can be used with some methods like WebClient.DownloadStringAsync

No. You can use it with any method that returns a Task. The XxxxAsync() methods are just precooked ones in the .NET framework for common operations that take time. Like downloading data from a web server.

Async/Await vs Threads Scenario

The main difference between the two functions is that the first one is performed by the calling thread, while the second one is called by a free thread in the thread pool.

Someone on stackoverflow explained me once the difference using the metaphor (Alas I don't know his name anymore).

Suppose you want to make breakfast. You need to toast some bread and boil some eggs.

You could put some bread in the toaster, wait until the bread is toasted, remove the toasted bread from the toaster and start boiling water. Once the water boils you add the eggs, wait some time and remove the eggs from the water and put out the fire. This is a typical example of synchronous execution.

On the other hand, after you put the bread in the toaster, you could already start boiling water. Wait until either the bread is toasted or the water is boiling. In the first case you'll remove the bread and wait for the water to boil. In the latter case you'll put the eggs in the water and wait until either the bread is toasted or the eggs are ready. As soon as you have to await for something to happen you look around whether you can do something else to speed up the process: a typical example of asynchronous execution, but still performed by one thread. This is the method async-await uses, the method used by your first function.

The most expensive method is hire a cook who you will order to toast some bread while you start heating the water to boil some eggs: concurrent processing performed by several threads.

If you have a thread that has only one thing to do, it's not wise if that thread hires a cook to do the thing and let that thread wait until the cook is ready: why not let the thread do the thing?

So if your thread has nothing else to do, use your first function.

However this first function can only be called if your thread is running in an async function itself. If your thread is not in an async function and it can do something meaningful while your bread is being toasted, consider using your second method.

Why async/await performs better than threads if it is just a wrapper around them?

There is no reason to expect Node to be faster than a server written in Java. Why do you think it might be?

It seems the other answers here (so far) are explaining the benefits of asynchronous programming in JS compared to single-threaded synchronous operations -- that's obvious, but not the question.

The key point everyone agrees on is that certain operations are inherently slow (e.g.: waiting for network requests, waiting for disk/database access), and it's efficient to let the CPU do something else while such operations are in flight. Using several threads in your application is one well-established way to do that; but of course that's only possible in languages that give you threads. Many traditional server implementations (in Java, C, C++, ...) use one thread per request (or, equivalently, a thread pool to distribute incoming requests over). These threads can block waiting for, say, the database -- that's okay, the operating system will put the waiting thread to sleep and let the CPU work on another thread (handling another request) in the meantime. The end result is fairly similar to what you get with Node.

JavaScript, of course, doesn't make threads available to the programmer. But instead, it has this concept of scheduling requests with the JavaScript engine and providing a callback to be invoked upon completion of the request. That's how the overall system behaves similarly to a traditional threaded programming language: user code can say "do this stuff, then schedule a database access, and when the result is available, continue with this [callback] code here", and while waiting for the database request, the CPU gets to execute some other code. What you want to avoid is the CPU sitting around idly waiting while there is other work waiting for the CPU to have time for it, and both approaches (Java threads and JavaScript callbacks) accomplish that.

Finally, async/await (just like Promises) are indeed just syntactic sugar that make it easier to write callback-based code. Code using async/await isn't any faster than old-style code using callbacks directly, just prettier and less error-prone. It also isn't any faster than a (well-written) Java-based server.

Node.js is popular because it's convenient to use the same language for the client and server parts of an app. From a performance point of view, it's not better than traditional alternatives, it's just also not worse (or at least not much; in practice how efficiently you design your app matters more than whether you implement it in Java or JavaScript). Don't fall for the hype :-)

What is the difference between asynchronous programming and multithreading?

Your misunderstanding is extremely common. Many people are taught that multithreading and asynchrony are the same thing, but they are not.

An analogy usually helps. You are cooking in a restaurant. An order comes in for eggs and toast.

  • Synchronous: you cook the eggs, then you cook the toast.
  • Asynchronous, single threaded: you start the eggs cooking and set a timer. You start the toast cooking, and set a timer. While they are both cooking, you clean the kitchen. When the timers go off you take the eggs off the heat and the toast out of the toaster and serve them.
  • Asynchronous, multithreaded: you hire two more cooks, one to cook eggs and one to cook toast. Now you have the problem of coordinating the cooks so that they do not conflict with each other in the kitchen when sharing resources. And you have to pay them.

Now does it make sense that multithreading is only one kind of asynchrony? Threading is about workers; asynchrony is about tasks. In multithreaded workflows you assign tasks to workers. In asynchronous single-threaded workflows you have a graph of tasks where some tasks depend on the results of others; as each task completes it invokes the code that schedules the next task that can run, given the results of the just-completed task. But you (hopefully) only need one worker to perform all the tasks, not one worker per task.

It will help to realize that many tasks are not processor-bound. For processor-bound tasks it makes sense to hire as many workers (threads) as there are processors, assign one task to each worker, assign one processor to each worker, and have each processor do the job of nothing else but computing the result as quickly as possible. But for tasks that are not waiting on a processor, you don't need to assign a worker at all. You just wait for the message to arrive that the result is available and do something else while you're waiting. When that message arrives then you can schedule the continuation of the completed task as the next thing on your to-do list to check off.

So let's look at Jon's example in more detail. What happens?

  • Someone invokes DisplayWebSiteLength. Who? We don't care.
  • It sets a label, creates a client, and asks the client to fetch something. The client returns an object representing the task of fetching something. That task is in progress.
  • Is it in progress on another thread? Probably not. Read Stephen's article on why there is no thread.
  • Now we await the task. What happens? We check to see if the task has completed between the time we created it and we awaited it. If yes, then we fetch the result and keep running. Let's suppose it has not completed. We sign up the remainder of this method as the continuation of that task and return.
  • Now control has returned to the caller. What does it do? Whatever it wants.
  • Now suppose the task completes. How did it do that? Maybe it was running on another thread, or maybe the caller that we just returned to allowed it to run to completion on the current thread. Regardless, we now have a completed task.
  • The completed task asks the correct thread -- again, likely the only thread -- to run the continuation of the task.
  • Control passes immediately back into the method we just left at the point of the await. Now there is a result available so we can assign text and run the rest of the method.

It's just like in my analogy. Someone asks you for a document. You send away in the mail for the document, and keep on doing other work. When it arrives in the mail you are signalled, and when you feel like it, you do the rest of the workflow -- open the envelope, pay the delivery fees, whatever. You don't need to hire another worker to do all that for you.

If async-await doesn't create any additional threads, then how does it make applications responsive?

Actually, async/await is not that magical. The full topic is quite broad but for a quick yet complete enough answer to your question I think we can manage.

Let's tackle a simple button click event in a Windows Forms application:

public async void button1_Click(object sender, EventArgs e)
{
Console.WriteLine("before awaiting");
await GetSomethingAsync();
Console.WriteLine("after awaiting");
}

I'm going to explicitly not talk about whatever it is GetSomethingAsync is returning for now. Let's just say this is something that will complete after, say, 2 seconds.

In a traditional, non-asynchronous, world, your button click event handler would look something like this:

public void button1_Click(object sender, EventArgs e)
{
Console.WriteLine("before waiting");
DoSomethingThatTakes2Seconds();
Console.WriteLine("after waiting");
}

When you click the button in the form, the application will appear to freeze for around 2 seconds, while we wait for this method to complete. What happens is that the "message pump", basically a loop, is blocked.

This loop continuously asks windows "Has anyone done something, like moved the mouse, clicked on something? Do I need to repaint something? If so, tell me!" and then processes that "something". This loop got a message that the user clicked on "button1" (or the equivalent type of message from Windows), and ended up calling our button1_Click method above. Until this method returns, this loop is now stuck waiting. This takes 2 seconds and during this, no messages are being processed.

Most things that deal with windows are done using messages, which means that if the message loop stops pumping messages, even for just a second, it is quickly noticeable by the user. For instance, if you move notepad or any other program on top of your own program, and then away again, a flurry of paint messages are sent to your program indicating which region of the window that now suddenly became visible again. If the message loop that processes these messages is waiting for something, blocked, then no painting is done.

So, if in the first example, async/await doesn't create new threads, how does it do it?

Well, what happens is that your method is split into two. This is one of those broad topic type of things so I won't go into too much detail but suffice to say the method is split into these two things:

  1. All the code leading up to await, including the call to GetSomethingAsync
  2. All the code following await

Illustration:

code... code... code... await X(); ... code... code... code...

Rearranged:

code... code... code... var x = X(); await X; code... code... code...
^ ^ ^ ^
+---- portion 1 -------------------+ +---- portion 2 ------+

Basically the method executes like this:

  1. It executes everything up to await

  2. It calls the GetSomethingAsync method, which does its thing, and returns something that will complete 2 seconds in the future

    So far we're still inside the original call to button1_Click, happening on the main thread, called from the message loop. If the code leading up to await takes a lot of time, the UI will still freeze. In our example, not so much

  3. What the await keyword, together with some clever compiler magic, does is that it basically something like "Ok, you know what, I'm going to simply return from the button click event handler here. When you (as in, the thing we're waiting for) get around to completing, let me know because I still have some code left to execute".

    Actually it will let the SynchronizationContext class know that it is done, which, depending on the actual synchronization context that is in play right now, will queue up for execution. The context class used in a Windows Forms program will queue it using the queue that the message loop is pumping.

  4. So it returns back to the message loop, which is now free to continue pumping messages, like moving the window, resizing it, or clicking other buttons.

    For the user, the UI is now responsive again, processing other button clicks, resizing and most importantly, redrawing, so it doesn't appear to freeze.

  5. 2 seconds later, the thing we're waiting for completes and what happens now is that it (well, the synchronization context) places a message into the queue that the message loop is looking at, saying "Hey, I got some more code for you to execute", and this code is all the code after the await.

  6. When the message loop gets to that message, it will basically "re-enter" that method where it left off, just after await and continue executing the rest of the method. Note that this code is again called from the message loop so if this code happens to do something lengthy without using async/await properly, it will again block the message loop

There are many moving parts under the hood here so here are some links to more information, I was going to say "should you need it", but this topic is quite broad and it is fairly important to know some of those moving parts. Invariably you're going to understand that async/await is still a leaky concept. Some of the underlying limitations and problems still leak up into the surrounding code, and if they don't, you usually end up having to debug an application that breaks randomly for seemingly no good reason.

  • Asynchronous Programming with Async and Await (C# and Visual Basic)
  • SynchronizationContext Class
  • Stephen Cleary - There is no thread well worth a read!
  • Channel 9 - Mads Torgersen: Inside C# Async well worth a watch!

OK, so what if GetSomethingAsync spins up a thread that will complete in 2 seconds? Yes, then obviously there is a new thread in play. This thread, however, is not because of the async-ness of this method, it is because the programmer of this method chose a thread to implement asynchronous code. Almost all asynchronous I/O don't use a thread, they use different things. async/await by themselves do not spin up new threads but obviously the "things we wait for" may be implemented using threads.

There are many things in .NET that do not necessarily spin up a thread on their own but are still asynchronous:

  • Web requests (and many other network related things that takes time)
  • Asynchronous file reading and writing
  • and many more, a good sign is if the class/interface in question has methods named SomethingSomethingAsync or BeginSomething and EndSomething and there's an IAsyncResult involved.

Usually these things do not use a thread under the hood.


OK, so you want some of that "broad topic stuff"?

Well, let's ask Try Roslyn about our button click:

Try Roslyn

I'm not going to link in the full generated class here but it's pretty gory stuff.

Why Use Async/Await Over Normal Threading or Tasks?

Yes, it is a syntactic sugar that makes dealing with threads much easier, it also makes the code easier to maintain, because the thread management is done by run-time. await release the thread immediately and allows that thread or another one to pick up where it left off, even if done on the main thread.

Like other abstractions, if you want complete control over the mechanisms under the covers, then you are still free to implement similar logic using thread signaling, etc.

If you are interested in seeing what async/await produces then you can use Reflector or ILSpy to decompile the generated code.

Read What does async & await generate? for a description of what C# 5.0 is doing on your behalf.

async await and threading

This is poorly advertised in the documentation, but the way that async/await works in a console application is very different than how it works in a UI application due to the lack of a synchronization context in a console application. This article describes details and gives a code sample of how add one so that async/await behaves in a more predictable way.

Normally, you're absolutely correct about async/await not necessarily entailing multithreading (although things like Task.Run do, in fact, cause the code in question to run in the thread pool), but (as described in the article I linked to) in a console application async/await could run anywhere.

My usual analogy for how async works when running on the same thread is to think of going to a restaurant with 9 other people (so 10 people total). When the waiter comes by, 9 of the people are ready and the 10th isn't. In this case, the waiter will take the other 9 people's orders first and then come back to the 10th person. If for whatever reason the 10th person's being really slow the waiter could always bring the orders back to the kitchen and come back when the 10th person's ready.

Clearly, there's no point to bringing in a second waiter just to wait for the 10th guy to be ready to order, and it would clearly be inefficient to make everyone else wait for the one guy to be ready. Adding extra waiters to the situation won't speed things up because the delay isn't being caused by a lack of waiters, it's caused by the 10th guy being slow to make up his mind (and there's nothing the wait staff can do about that).

Async await and threads

A Task does not necessarily represent an extra thread.

If you await a Task, you return the control flow to the caller of your method until "someone" sets the Task as completed.

If you start a Task via Task.Run() or Task.Factory.StartNew(), then the action you pass to these calls is executed on another thread (not a new one, but one from the ThreadPool).



Related Topics



Leave a reply



Submit