Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How does the runtime know when to spawn a thread when using "await"?

Tags:

c#

async-await

EDIT

I took Jon's comment and retried the whole thing. And indeed, it is blocking the UI thread. I must have messed up my initial test somehow. The string "OnResume exits" is written after SomeAsync has finished. If the method is changed to use await Task.WhenAll(t) it will (as expected) not block. Thanks for the input! I was first thinking about deleting the question because the initial assumption was just wrong but I think the answers contains valuable information that should not be lost.

The original post:

Trying to understand the deeper internals of async-await. The example below is from an Android app using Xamarin. OnResume() executes on the UI thread.

  • SomeAsync() starts a new task (= it spawns a thread). Then it is using Task.WaitAll() to perform a blocking wait (let's not discuss now if WhenAll() would be a better option).
  • I can see that the UI is not getting blocked while Task.WaitAll() is running. So SomeAsync() does not run on the UI thread. This means that a new thread was created.

How does the await "know" that it has to spawn a thread here - will it always do it? If I change the WaitAll() to WhenAll(), there would not be a need for an additional thread as fast as I understand.

// This runs on the UI thread.
async override OnResume()
{
  // What happens here? Not necessarily a new thread I suppose. But what else?
  Console.WriteLine ("OnResume is about to call an async method.");
  await SomeAsync();

  // Here we are back on the current sync context, which is the UI thread.
  SomethingElse();
  Console.WriteLine ("OnResume exits");
}

Task<int> SomeAsync()
{
var t = Task.Factory.StartNew (() => {
    Console.WriteLine("Working really hard!");
    Thread.Sleep(10000);
    Console.WriteLine("Done working.");
});
Task.WhenAll (t);

return Task.FromResult (42);
}
like image 716
Krumelur Avatar asked Apr 02 '14 07:04

Krumelur


People also ask

Does await spawn a thread?

The async and await keywords don't cause additional threads to be created. Async methods don't require multithreading because an async method doesn't run on its own thread.

Does async-await blocks the main thread?

Because await is only valid inside async functions and modules, which themselves are asynchronous and return promises, the await expression never blocks the main thread and only defers execution of code that actually depends on the result, i.e. anything after the await expression.

Does Task run create a new thread?

request thread (ASP.NET thread) starts the GetAsync method and calls DoComplexCalculusAsync() asynchronously. Inside DoComplexCalculusAsync(), Task. Run uses another new thread from thread pool to do the heavy calculations in the background.

How does async-await works?

The async keyword turns a method into an async method, which allows you to use the await keyword in its body. When the await keyword is applied, it suspends the calling method and yields control back to its caller until the awaited task is complete. await can only be used inside an async method.


2 Answers

Simple: it never spawns a thread for await. If the awaitable has already completed, it just keeps running; if the awaitable has not completed, it simply tells the awaitable instance to add a continuation (via a fairly complex state machine). When the thing that is being completed completes, that will invoke the continuations (typically via the sync-context, if one - else synchronously on the thread that is marking the work as complete). However! The sync-context could theoretically be one that chooses to push things onto the thread-pool (most UI sync-contexts, however, push things to the UI thread).

like image 169
Marc Gravell Avatar answered Oct 11 '22 22:10

Marc Gravell


I think you will find this thread interesting: How does C# 5.0's async-await feature differ from the TPL?

In short, await does not start any threads.

What it does, is just "splitting" the code into at the point where the, let's say, line where 'await' is placed, and everything that that line is added as continuation to the Task.

Note the Task. And note that you've got Factory.StartNew. So, in your code, it is the Factory who actually starts the task - and it includes placing it on some thread, be it UI or pool or any other task scheduler. This means, that the "Task" is usually already assigned to some scheduler when you perform the await.

Of course, it does not have to be assigned, nor started at all. The only important thing is that you need to have a Task, any, really.

If the Task is not started - the await does not care. It simply attaches continuation, and it's up to you to start the task later. And to assign it to proper scheduler.

like image 36
quetzalcoatl Avatar answered Oct 11 '22 22:10

quetzalcoatl