Let’s start this blog by defining what a synchronous method and an asynchronous method are for application developers.
- Synchronous: Control is regained when the work is complete. The executed function is blocked until that time.
- Asynchronous: The control is returned immediately.
Thanks to the keywords async and await, using asynchronous functions has become easier, faster, and generates a more readable and simpler code.
Take the following example:
public async Task DoSomethingAsync() { // In the real world, we would do something particular.. // For this example, we are only going to (asynchronously) wait 100ms await Task.Delay(100);
As you can see, the method contains the word async. This keyword allows for the word await to be used inside the method, and it modifies how its result is handled by the method. That’s it.
The method runs synchronously until it finds the word await, and that word is the one that takes care of asynchrony. Await is an operator that receives a parameter, and an awaitable (an asynchronous operation) behaves in this way:
- Examine the awaitable to see if it is complete. If this is the case, the method simply continues to run synchronously as any other method.
- On the contrary, if await detects that the awaitable isn’t completed, it behaves asynchronously: it tells the awaitable to run the rest of the method after the call is completed, and it goes back to the asynchronous method to which it has been called.
- After a while, when the awaitable is complete, you run what is left of the async method. Unless otherwise indicated, what is left of the async method is executed in a context that was captured prior to the return of the await.
The async method is on pause until the awaitable is complete (wait), but the thread is not blocked by this call (it is asynchronous).
Something important about awaitables: the type of data returned is awaitable, not the method itself. This means that one can await the result of an async method because it returns a Task, not for being marked as async.
A method can return async Task, Task, or void. It is advised to return void only when necessary since the Tasks are awaitable, while void is not. For example, using async void in an Event Handler is not awaitable.
About the context mentioned above, previously it was said that at the return point, the awaitable keeps the context in which it was originally executed. But what context would it be?
- If you are in a UI thread, then it is the context of UI.
- If you are responding to an ASP.NET request, then it is a context of an ASP.NET request.
- Otherwise, it is usually the thread pool context.
// ASP.NET WinForms example protected async void MyButton_Click(object sender, EventArgs e) // As we waited asynchronously, the ASP.NET thread is not blocked by the descent of the file. // This enables the thread to handle other requests while the method awaits the outcome of awaitable . DownloadFileAsync await ( ... ) ; // As we return to a context of ASP.NET , we can access the current context. Response. Write ( "File downloaded !"); }
If for some reason it is not required to have the original context, it is indicated to the awaitable not to capture and save the current context by configuring the Await method and passing false as a parameter.
Private async Task DownloadFileAsync(string fileName) { var fileContents = await DownloadFileContentsAsync(fileName).ConfigureAwait(false); // Due to ConfigureAwait ( false) , here we are not in the original context // On the contrary , we are running in the thread pool await WriteToDiskAsync ( fileName , filecontents ) .ConfigureAwait ( false); } DownloadFileButton_Click private async void ( object sender, EventArgs e) { await DownloadFileAsync ( fileNameTextBox.Text ) ; // As we return within the context of UI , we can access to the items resultTextBox.Text = "File downloaded !"; }
- DownloadFileButton_Clickit began its implementation in the context of UI and was called DownloadFileAsync.
- DownloadFileAsyncalso started in the context of UI but then out of context by calling it ConfigureAwait (false).
- The rest of the DownloadFileAsyncmethod now runs in the context thread pool.
- However, DownloadFileAsynccompletes execution and effectively summarizes DownloadFileButton_Click which is summarized in the context of UI.
In the following image, it clarifies the operation of the different calls inside an execution of an asynchronous code:
Source: Microsoft
Differences between Task.Run and async await
The question is: When should you use Task.Run?
Task.Run must be used to get methods linked exclusively to a high use of CPU (for documents search CPU-bound methods).
An example of a non-recommended use:
class MyService { public async Task RetrieveValueAsync(int id) { await Task.Run(() => { // Make an assignment.. // Access to the DB, web request, etc. Thread.Sleep(500); return 42; }); } }
From the UI perspective, this one is not blocked during the process of the method. The problem is that it is not truly asynchronous; it executes a blocking code thereby blocking another thread while the operation is in process.
The recommended approach is to first change the blocking code to an asynchronous call (for example, using Queries asynchronous in EntityFramework). In the example, Thread.Sleep is replaced with Task.Delay.
class MyService { public async Task RetrieveValueAsync(int id) { // Converted to a non-blocking code. // Access to DBs, web request, etc. await Task.Delay(500); return 42; } }
How does await affect the use of Task.Run?
Let’s say we have the following code:
class MyService { public Task CalculateMandelbrotAsync() { return Task.Run(() => { // A lot of work to make here! for (int i = 0; i != 10000000; ++i) ; return 42; }); } } ... public class Mandelbrot { public async Task IndexAsync() { var result = await myService.CalculateMandelbrotAsync(); } }
With a synchronous implementation, only one thread is used. This is true for ASP.NET, but this is what happens to the code:
- The request begins to be processed in the Thread of ASP.NET.
- Run starts an assignment in the thread pool to make calculations. The thread pool of ASP.NET has to handle losing (unexpectedly) one of its threads for the process of the request.
- The thread of the original request goes back to the thread pool of ASP.NET.
- When the calculation finishes, the thread completes the request and goes back to the thread pool of ASP.NET, which has to deal to recover (unexpectedly) another thread.
This works, but it isn’t entirely efficient.
There are (at least) two problems of efficiency using await with Task.Run in ASP.NET:
- There is an extra “thread switching” (unnecessary) to the thread of Task.Run in the thread pool. In the same way, when the thread finishes the request, it has to go back to the original context (which has an overhead).
- The creation of (unnecessary) extra trash. Asynchronous programming is about commitment; it gets a better response in exchange for bigger memory use. In this case, it ends up creating more trash for the asynchronous operation, which is unnecessary. ASP.NET is not capable of finishing the request in an anticipated way, for example, if the client is disconnected or if a timeout is produced in the request. In the synchronous case, ASP.NET knows the thread of the request and can abort it; in the asynchronous case, ASP.NET is not conscious that the secondary thread pool is for that request.
The use of Task.Run like a wrapper for asynchronous methods
Another non-recommended way is the Task.Run because it’s like a wrapper for asynchronous methods. If we make an API, and we want to expose not only a synchronous implementation but also an asynchronous, we may find a code like this:
class MyService { public int CalculateMandelbrot() { // Tons of work to do in here! for (int i = 0; i != 10000000; ++i); return 42; } public Task CalculateMandelbrotAsync() { return Task.Run(() => CalculateMandelbrot()); } }
A “false asynchronous method” is being created because what it is doing is wrapping a synchronous method inside another thread (different from the UI).
A recommended implementation is shown in Figure 6. Due to the initial problem, which was not blocking the thread of UI, the solution falls in the same UI and not in the service implementation.
class MyService { public int CalculateMandelbrot() { // Tons of work to do in here! for (int i = 0; i != 10000000; ++i); return 42; } } ... private async void GetMandelbrot() //Called from UI { await Task.Run(() => myService.CalculateMandelbrot()); }
Task.Run in Libraries
Threads, especially threads of the thread pool, are globally shared resources that belong to the development of the application. The creator of the library must never use Task.Run or any other method that creates threads because it is the app creator’s responsibility to decide when or if to create additional threads.
Task.Run in the client
There are a lot of reasons for the client to use Task.Run, but they all exist at the application level. The library code doesn’t have enough context to decide if a given operation must be moved to a background thread. The app code could already be in a background thread when the call to the library function is invoked, or it could be interacting with the UI in the case that it needs to stay in the thread of UI.
If the Task.Run is used in the library, it will be creating obstacles that prevent it from using the thread pool in an optimal way.
¿What is .Result?
The Result of a Task is a blocking property. If you try to access the task before it completes, the active thread will be blocked until the task is completed and the return value is available. In most cases, you can access the return value by await instead of access to the property directly.
public class MyController : ApiController { public string Get() { var jsonTask = GetJsonAsync(...); return jsonTask.Result.ToString(); // Blocking. } }
Replace with this:
public class MyController : ApiController { public async Task Get() { var json = await GetJsonAsync(…); return json.ToString(); } }
TO SUM UP
- Run must be avoided in the implementation of the asynchronous methods, especially if you are programming an API.
- The UI process must be free to decide how it will consume the asynchronous method, invoking directly or executing the assignment in background using Task.Run.
- Use Task.Run to call processes that are related to CPU consumption.
Sources:
- Async API Design
- Asynchronous Programming with Async and Await (C# and Visual Basic)
Don’t block on async code - Taskrun etiquette and proper usage
- Async and Await
Comments? Contact us for more information. We’ll quickly get back to you with the information you need.