Logo Questions Linux Laravel Mysql Ubuntu Git Menu

async await usages for CPU computing vs IO operation?

I already know that async-await keeps the thread context , also handle exception forwarding etc.(which helps a lot).

But consider the following example :

/*1*/   public async Task<int> ExampleMethodAsync()
/*2*/   {
/*3*/       var httpClient = new HttpClient(); 
/*5*/       //start async task...
/*6*/       Task<string> contentsTask = httpClient.GetStringAsync("http://msdn.microsoft.com");
/*8*/       //wait and return...  
/*9*/       string contents = await contentsTask;
/*11*/       //get the length...
/*12*/       int exampleInt = contents.Length;
/*14*/       //return the length... 
/*15*/       return exampleInt;
/*16*/   }

If the async method (httpClient.GetStringAsync) is an IO operation ( like in my sample above) So - I gain these things :

  • Caller Thread is not blocked
  • Worker thread is released because there is an IO operation ( IO completion ports...) (GetStringAsync uses TaskCompletionSource and not open a new thread)
  • Preserved thread context
  • Exception is thrown back

But What if instead of httpClient.GetStringAsync (IO operation) , I have a Task of CalcFirstMillionsDigitsOf_PI_Async (heavy compute bound operation on a sperate thread)

It seems that the only things I gain here is :

  • Preserved thread context
  • Exception is thrown back
  • Caller Thread is not blocked

But I still have another thread ( parallel thread) which executes the operation. and the cpu is switching between the main thread and the operation .

Does my diagnostics is correct?

like image 257
Royi Namir Avatar asked Mar 23 '23 15:03

Royi Namir

2 Answers

Actually, you only get the second set of advantages in both cases. await doesn't start asynchronous execution of anything, it's simply a keyword to the compiler to generate code for handling completion, context etc.

You can find a better explanation of this in '"Invoke the method with await"... ugh!' by Stephen Toub.

It's up to the asynchronous method itself to decide how it achieves the asynchronous execution:

  • Some methods will use a Task to run their code on a ThreadPool thread,
  • Some will use some IO-completion mechanism. There is even a special ThreadPool for that, which you can use with Tasks with a custom TaskScheduler
  • Some will wrap a TaskCompletionSource over another mechanism like events or callbacks.

In every case, it is the specific implementation that releases the thread (if one is used). The TaskScheduler releases the thread automatically when a Task finishes execution, so you get this functionality for cases #1 and #2 anyway.

What happens in case #3 for callbacks, depends on how the callback is made. Most of the time the callback is made on a thread managed by some external library. In this case you have to quickly process the callback and return to allow the library to reuse the method.


Using a decompiler, it's possible to see that GetStringAsync uses the third option: It creates a TaskCompletionSource that gets signalled when the operation finishes. Executing the operation is delegated to an HttpMessageHandler.

like image 117
Panagiotis Kanavos Avatar answered Mar 31 '23 14:03

Panagiotis Kanavos

Your analysis is correct, though the wording on your second part makes it sound like async is creating a worker thread for you, which it is not.

In library code, you actually want to keep your synchronous methods synchronous. If you want to consume a synchronous method asynchronously (e.g., from a UI thread), then call it using await Task.Run(..)

like image 31
Stephen Cleary Avatar answered Mar 31 '23 14:03

Stephen Cleary