Krumelur
Krumelur

Reputation: 33058

How does the runtime know when to spawn a thread when using "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.

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);
}

Upvotes: 6

Views: 197

Answers (2)

quetzalcoatl
quetzalcoatl

Reputation: 33546

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.

Upvotes: 1

Marc Gravell
Marc Gravell

Reputation: 1063198

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).

Upvotes: 6

Related Questions