Consider the following:
private async void btnSlowPoke_Click(object sender, EventArgs e)
{
await DoItAsync();
}
private async Task<int> SomeLongJobAsync()
{
for (int x = 0; x < 999999; x++)
{
//ponder my existence for one second
await Task.Delay(1000);
}
return 42;
}
public async Task<int> DoItAsync()
{
Console.Write("She'll be coming round the mountain");
Task<int> t = SomeLongJobAsync(); //<--On what thread does this execute?
Console.WriteLine(" when she comes.");
return await t;
}
DoItAsync()
executes.SomeLongJobAsync()
starts.WriteLine
in DoItAsync()
executes.DoItAsync()
pauses while SomeLongJobAsync()
works away until it's done.SomeLongJobAsync()
completes, so DoItAsync()
returns.Meanwhile, the UI is responsive.
On what thread does SomeLongJobAsync()
execute?
As you probably recall, await captures information about the current thread when used with Task. Run . It does that so execution can continue on the original thread when it is done processing on the other thread.
The await operator suspends evaluation of the enclosing async method until the asynchronous operation represented by its operand completes. When the asynchronous operation completes, the await operator returns the result of the operation, if any.
await releases the current thread, but NOT to the thread pool. The UI thread doesn't come from the thread pool. If you run asynchronous method, e.g. ExecuteScalarAsync without async, await keywords, then this method will run asynchronously no matter what. The calling thread won't be affected .
No, async await is just made to allow code to run whilst something else is blocking, and it doesn't do Task. Run, or start a new thread.
An async
method fired by the GUI
thread will execute on the same thread, whenever there are CPU operation to execute. Other async
methods start running on the calling thread and continue on a ThreadPool
thread.
SomeLongJobAsync
starts executing on the calling thread (the one that printed "She'll be coming round the mountain") up until it reaches an await
. Then a task is returned that represents the asynchronous operation + the continuation after it. When the entire operation is done the task will complete (unless it completes prematurely due to an exception or cancellation).
When Task.Delay(1000)
itself is "executing" there is no thread, because none is needed. And when finally Task.Delay(1000)
ends, a thread is needed to resume on. Which thread it is depends on the SynchronizationContext
(by default there is none so the thread is a ThreadPool
thread, but in a GUI
application it's the singe GUI thread, more here). That thread executes the rest of the code until it reaches another asynchronous point (i.e. another await
) and so forth and so forth.
The important thing to realise is that async is not about creating threads, it's about replacing what used to be a blocking call by one that returns a continuation. A thread blocks when it is placed on a queue and it can then do nothing until the thing it blocked on becomes available (or a timeout or exception, etc). Blocking the UI thread is a terrible thing to do.
By contrast a continuation contains enough information captured at a point in the program for the thread to carry on ("continue") from that exact point at a later time. Obviously there is special stuff needed in all the Async calls for that to work, but that's what it does. It freezes the thread state into a continuation, parks that somewhere, returns immediately (so no blocking) and then (somehow) starts again from that state at a later time when the required information is available.
For that reason, you can assume that the work for both the async method and the "long job" will be done on the same thread, just not at the same time, and that the operating system will choose a good time to decide when to make those choices.
In practice there is a difference between threads that have a message pump (UI threads) and others, and there is the possibility of work being moved to a different thread, and there are various features in the Task
, the SynchronizationContext
and thread pools to support more advanced scenarios.
But I think the key to answering your question and for you to understand is this subtle use of something new called a continuation, and how it can capture the state of a program at one time for use later. Continuations have been used in functional languages for a long time, and are in some ways related to the concepts of futures and promises in other languages. Once you think in these terms you can forget about threads entirely.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With