Task.Run Vs TaskCompletionSource Vs Task.Factory.FromAsync

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 GetAsync of 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 TaskCompletionSource.

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

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 TaskCompletionSource better.

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 FromAsync with Task.Run.

Summary

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.

Share...
 

Hamid Mosalla

Hi, I'm Hamid ("Arman"). I'm a software developer with 8+ years of experience in C#, .NET Core, Software Architecture and Web Development. I enjoy creating dev tools, contributing to open-source projects, and sharing insights on my blog. Outside of tech, I’m into indie cinema, classical music and abstract art.

 

3 thoughts on “Task.Run Vs TaskCompletionSource Vs Task.Factory.FromAsync

Leave a Reply

Your email address will not be published. Required fields are marked *