Task.Run vs Async Await

Martin Spasiuk
Hexacta Engineering
6 min readJun 1, 2016

By Martin Spasiuk and Nicole Guivisdalsky

Let’s start by defining what a synchronous method and an asynchronous method is for application developers.

Synchronous: You regain control 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 became easier, faster and generates a more readable and simple code.

Take the following example:

As you can see, the method contains the word “async”. This keyword allows that inside the method the word “await” can be used, and 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: 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 is 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 (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.

About the context mentioned above, previously it was mentioned 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 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, usually is the pool thread context.

If for some reason, is not required to have the original context, in that case, it is indicated to the awaitable not to capture and save the current context, by configuring Await method, passing false as a parameter.

  • DownloadFileButton_Click it began its implementation in the context of UI, and called DownloadFileAsync.
  • DownloadFileAsync also started in the context of UI, but then out of context by calling ConfigureAwait ( false).
  • The rest of the DownloadFileAsync method now runs in the context thread pool.
  • However, when DownloadFileAsync completes execution and summarizes DownloadFileButton_Click effectively 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: https://msdn.microsoft.com/en-us/library/hh191443.aspx

Differences between Task.Run vs async await

The question is: When you should 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:

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, blocking another thread while the operation is in process.

The approach recommended is to first change the blocking code to an asynchronous call (for example, using Queries asynchronous in EntityFramework). In the example it is replaced Thread.Sleep for Task.Delay.

How does it affect the use of Task.Run with await?
Let’s say we have the following code.

With a synchronous implementation, only one thread is used. This is true for ASP.NET, but what happens to the code is the following:

  • The request begins to be processed in the Thread of ASP.NET.
  • Task.Run starts an assignment in the thread pool to make calculations. The thread pool of ASP.NET has to handle loosing (unexpectedly) one of its thread 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) four problems of efficiency using await with Task.Run in ASP.NET:

  • There is an extra “thread switching” (unnecesary) 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 of commitment, it gets a better response in exchange of a 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 threadpool is “for” that request.

The use of Task.Run like a wrapper for asynchronous methods

Another non recommended way is the Task.Run, it’s like wrapper for asynchronous methods. If we make an API we want to expose not only a synchronous implementation but also an asynchronous, we could find a code like this:

It is being created a “false asynchronous method” because what it is doing is wrap a synchronous method inside another thread (different from the UI)

A recommended implementation is the one that figures ahead. 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.

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 is the app creator’s responsability decide when, or even create aditionals 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 that 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 would be creating obstacles that prevent 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 to the task before it completes, the active thread is blocked until the task is complete and the return value is available. In most cases, you can access the return value by await instead of access to the property directly.

Replace for:

SUMMARY

  • Task.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.

Source:

http://www.infoq.com/articles/Async-API-Design
https://msdn.microsoft.com/en-us/library/hh191443.aspx
http://blog.stephencleary.com/2012/07/dont-block-on-async-code.html
http://blog.stephencleary.com/2013/10/taskrun-etiquette-and-proper-usage.html
http://blog.stephencleary.com/2012/02/async-and-await.html

Originally published at www.hexacta.com on June 1, 2016.

--

--

Martin Spasiuk
Hexacta Engineering

Software Architect & CSM. Like to try new stuff to make my life easier.