You can take advantage of asynchronous programming to perform resource-intensive operations without having to block on executing thread of your application. This increases the throughput while at the same time ensuring that the user interface is responsive.
However, you should be aware of the concepts of asynchrony, how it all actually works, and the best practices involved to avoid potential deadlocks and slow performance when using asynchrony the wrong way.
This is a continuation of the series of my articles on the best practices on asynchronous programming. The key take away from this series of articles would be the best practices and strategies that can be followed to achieve scalability and performance benefits when working with asynchrony in .Net. Here’s the link to the first part in this series.
Avoid creating create asynchronous wrappers with Task.Run method
Let’s check out a use case. Consider the following piece of code that illustrates an asynchronous wrapper using the Task.Run method.
public async Task<string> DownloadStringFromWebAsync(Uri uri)
{
return await Task.Run(() =>
{
using (var webClient = new WebClient())
{
return webClient.DownloadString(uri);
}
});
}
Note that any method that is marked with the async keyword can have one or more await keywords inside. The “await” keyword is used in an async method to inform the compiler that the method can have a suspension and resumption point. The commonly used awaitable types are Task and Task<T>.
Well, this is an example of bad code! It would execute synchronously and would block on the background thread. What you’re doing here is just using a thread from the thread pool. However, you would be consuming more resources here and you would not get any scalability benefit at all. Remember that an asynchronous method should always have synchronous method signature and you should not wrap a synchronous method with the Task.Run method to achieve asynchrony — it would never ever help!
If you would like to leverage the performance and scalability benefits of asynchrony, you should implement the asynchronous programming pattern using the async and await keywords as shown in the modified code snippet below.
public async Task<string> DownloadStringFromWebModifiedAsync(Uri uri)
{
using (var httpClient = new HttpClient())
{
return await httpClient.GetStringAsync(uri);
}
}
The above code is asynchronous and wouldn’t block the current thread.
Avoid using Task.Run in ASP.Net applications
You should avoid using thread pool threads (other than the request thread that is given to you by the ASP.Net runtime) when working in ASP.Net applications. You would not gain any performance benefit by using the Task.Run method in ASP.Net as it would spawn a new thread. Refer to the code snippet given below.
public async Task<Author> GetAuthor(Author authorId)
{
return await Task.Run(() => GetAuthorById(authorId));
}
Essentially, when using Task.Run in your Web API controller in this way, you would just be releasing one thread and blocking another. Creating additional threads in this way would increase the overhead actually. Hence, you should avoid using the Task.Run method in ASP.Net. If you are using Task.Run method in Web API, you are limiting scalability by passing the load to another thread from the thread pool unnecessarily. Incidentally, your Web API request already uses a ThreadPool thread. So, usage of Task.Run method is considered a bad practice when working with ASP.Net applications.
Avoiding deadlocks
If you are consuming a method that takes advantage of asynchronous programming features available in the TPL in a blocking manner, you might encounter a deadlock situation. In essence, a deadlock can occur if you are blocking on such a method if you are waiting for the Task to complete using Wait or trying to retrieve of the operation using the Result property of the Task object.
You can take advantage of ConfigureAwait(false) to avoid deadlocks. Note that the moment your async method encounters the await operator, the method is blocked. However, the thread is returned to the thread pool and when your asynchronous method is ready to continue again, the runtime will pick up any thread from the thread pool to resume the call. The ConfigureAwait(false) method call is used to ensure that the Task continuation doesn’t resume on the captured context. In other words, this call ensures that the continuation will not execute on the caller’s context.
This article is published as part of the IDG Contributor Network. Want to Join?