There’s a lot of small intricate details that one can miss when we deal asynchronous programs. Even though these days far more superior APIs exist that make things a lot easier. For example a lot of people think if we want to call any synchronous code asynchronously, we just simply wrap it in Task.Run. It’s true that
Task.Run run the operation asynchronously, but it’s not the same as the natively async methods.
Another important issue arises when we need to run a code that is natively async but belong to older model of asynchrony. Again the first thing that comes to mind is that we should wrap this code inside
Task.Run. But there are better options that allow us to convert the old async model, to new one. All of this can be achieved without the overhead of creating a new thread. So in this post I’m going to go into more detail regarding these scenarios.
Task.Run Is Not A Silver Bullet
Task.Run is one of the most miss used construct in .Net async API. The most important thing to understand about
Task.Run is how it differs with normal async methods. In a normal async method, for example the method
HttpClient, when we await this method, no thread is wasted on waiting for response. What happens is when the program reaches the await keyword, it dispatches the request for the information and yield itself back to the caller. There’s good article by Stephen Cleary that goes into more detail about this, you can read it here.
That is different when we use
Task.Run. What Task.Run does is that it create a new thread specifically to execute the operation. While
Task.Run might be useful to make our application more responsive (we don’t want to block the UI), in other scenarios there’s always a better option. The best use for
Task.Run is when we have some CPU intensive task, something like what you see below.
Otherwise you better use the internal async API, or maybe you don’t need asynchronous API. If none of these cases were true, you should use
Create Awaitable Task From Old Synchronous Or Asynchronous API With TaskCompletionSource
TaskCompletionSource is different from Task.Run, in that it doesn’t create a new thread. It primarily exist so we can create a task and hand it out to consumers for use. consumer doesn’t have to know or don’t care about how this task is constructed. One example is when we have an old async API, and we want to create an awaitable task from that and hand it out to users. By doing that we hide the complexity that is involved with using that old async API. Consider this example.
The most important thing to understand here is creation of
TaskCompletionSource on line 6. Setting the result property of the task of line 15, and returning the task. So start from the beginning when
TaskCompletionSource is first created, the status of the underlying task object is set to
WaitingForActivation. This cause the task to yield the control back to caller when someone
await this task. However when we call the
SetResult method, the status of task object is changed to
Completed. So the control resume where it left off and continue the operation. It does all of this without wasting another thread pool thread unlike
Note that it doesn’t matter how we construct this task. It can be that we have an old async API or we have some API that doesn’t support async and we want to make it asynchronous. All of this accomplished without wasting any thread pool thread.
Here’s another example that turns the Process object which is synchronous to asynchronous one.
If you need more detail about this, here’s an article by Stephen Toub. Also take a look here and here, these are other questions on Stackoverflow which can help to understand
Create Awaitable Task From Old APM Style Asynchronous API Using TaskFactory.FromAsync
If you have a APM style API and you wan to use it with modern async/await model, you should not use
Task.Run to do it. By doing that you’re wasting CPU thread which can be used for other tasks. What you should be doing instead is to ues
FromAsync to await what you want to call and return the result. Here’s an example that read the file and return string.
Here we asynchronously used the stream and read the information and returned the result. Here’s a good question that is worth reading which compares
In this post I explained why using
Task.Run is not always a good idea. I also explain how
Task.Run is different with normal async (naturally async) method. I also introduced mechanisms in .Net that let us turn a synchronous API to an asynchronous one or an old async style of API, to new way of doing it.