Where are the "expected" tasks performed?

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; } 
  • The first entry in DoItAsync() .
  • SomeLongJobAsync() .
  • Runs WriteLine in DoItAsync() .
  • DoItAsync() stops while SomeLongJobAsync() running until it is executed.
  • SomeLongJobAsync() ends, so DoItAsync() returns.

At the same time, the user interface is responsive.

What thread does SomeLongJobAsync() ?

+6
source share
4 answers

Short answer

The async method initiated by the GUI thread will execute in the same thread whenever a CPU operation is performed. Other async methods run in the calling thread and continue in the ThreadPool thread.

Long answer

SomeLongJobAsync starts execution on the calling thread (the one that printed "She will approach the mountain") until it reaches await . Then a task is returned representing the asynchronous operation + continued after it. When the entire operation is completed, the task will end (if it does not end prematurely due to an exception or cancellation).

When Task.Delay(1000) itself executes โ€œ there is no thread , because there is no need. And when Task.Delay(1000) finally finishes, the thread needs to be resumed. Which thread depends on SynchronizationContext (by default there is none, therefore the thread is a ThreadPool thread but in the GUI attach it to the single line of the GUI, more here .) This thread executes the rest of the code until it reaches another asynchronous point (i.e. Another await ), etc. Etc.

+9
source

Itโ€™s important to understand that async is not about creating threads, but replacing what used to be a blocking call that returns a continuation. The thread is blocked when it is placed in the queue, and it can do nothing until the item that it has blocked becomes available (or timeout, exception, etc.). UI thread blocking is a terrible thing.

In contrast, this continuation contains sufficient information recorded at the point in the program to continue the flow ("continue") from this exact point later. Obviously, all asynchronous calls require special material to work, but this is what it does. It freezes the state of the flow in the future, parking somewhere, returns immediately (so it is not blocked), and then (somehow) starts again from this state later, when the required information is available.

For this reason, you can assume that the work for both the async method and the โ€œlong workโ€ will be performed in the same thread, but not at the same time, and that the operating system will choose the right time to decide when to do these elections.

In practice, there is a difference between threads that have a message pump (user interface threads) and others, and there is the ability to move work to another thread, and Task has various functions, SynchronizationContext and thread pools to support more complex scenarios.

But I think that the key to answering your question and for you to understand is the subtle use of something new, called a continuation, and how it can capture the state of a program at a time for use later. Continuations have long been used in functional languages โ€‹โ€‹and, in a sense, are related to the concepts of futures and promises in other languages. Once you think that in these terms you can completely forget about flows.

+1
source

SomeLongJobAsync starts execution on the thread that called it, and if it is, the current SynchronizationContext stored in the state machine generated by the await mechanism.

Upon completion of the first await continuation of the method is published in the current SynchronizationContext . In a graphical application, this means that the continuation is performed in the user interface thread. There is no SyncrhronizatonContext in the console application, so the continuation is done in the thread pool thread.

You can verify this by printing ManagedThreadId Thread.CurrentThread as your program Thread.CurrentThread . Consider this modified version of your code (which I ran from a console application on Linqpad):

 private async void btnSlowPoke_Click(object sender, EventArgs e) { await DoItAsync(); } private async Task<int> SomeLongJobAsync() { Console.WriteLine("Start SomeLongJobAsync, threadId = " + Thread.CurrentThread.ManagedThreadId); for (int x = 0; x < 9; x++) { //ponder my existence for one second await Task.Delay(1000); Console.WriteLine("Continue SomeLongJobAsync, threadId = " + Thread.CurrentThread.ManagedThreadId); } return 42; } public async Task<int> DoItAsync() { Console.WriteLine("She'll be coming round the mountain, threadId = " + Thread.CurrentThread.ManagedThreadId); Task<int> t = SomeLongJobAsync(); //<--On what thread does this execute? Console.WriteLine(" when she comes., threadId = " + Thread.CurrentThread.ManagedThreadId); return await t; } void Main() { btnSlowPoke_Click(null, null); Console.ReadLine(); } 

Exiting the console application:

 She'll be coming round the mountain, threadId = 21 Start SomeLongJobAsync, threadId = 21 when she comes., threadId = 21 Continue SomeLongJobAsync, threadId = 11 Continue SomeLongJobAsync, threadId = 11 Continue SomeLongJobAsync, threadId = 11 Continue SomeLongJobAsync, threadId = 11 Continue SomeLongJobAsync, threadId = 11 Continue SomeLongJobAsync, threadId = 12 Continue SomeLongJobAsync, threadId = 12 Continue SomeLongJobAsync, threadId = 12 Continue SomeLongJobAsync, threadId = 12 

As you can see, the method started runnung in thread 21, but as each of them completed, it continued in the thread pool thread and was not always the same. In this case, 11, 12. If I ran this in a Windows Forms application, it will be as follows:

Exit the Windows Forms application:

 She'll be coming round the mountain, threadId = 8 Start SomeLongJobAsync, threadId = 8 when she comes., threadId = 8 Continue SomeLongJobAsync, threadId = 8 Continue SomeLongJobAsync, threadId = 8 Continue SomeLongJobAsync, threadId = 8 Continue SomeLongJobAsync, threadId = 8 Continue SomeLongJobAsync, threadId = 8 Continue SomeLongJobAsync, threadId = 8 Continue SomeLongJobAsync, threadId = 8 Continue SomeLongJobAsync, threadId = 8 Continue SomeLongJobAsync, threadId = 8 
+1
source

Running in the same thread. The documentation explains:

Asynchronous and pending keywords do not create additional threads. Asynchronous methods do not require multithreading because the async method does not start in its thread. The method works in the current synchronization context and uses the time in the stream only when the method is active. You can use Task.Run to move the processor-related work to the background thread, but the background thread does not help the process, which just waits for the results to become available.

0
source

Source: https://habr.com/ru/post/971390/


All Articles