다음을 통해 공유


C#: Avoiding Deadlock in Async/Await

Introduction

I have recently been working with Windows Forms application which must have the ability to work with MDF database. Great! I have created a new project, added a new service based database (.mdf) and I have the background of .NET 4.5 (C# 5.0) which gives me the ability to work with both async and await keywords.

You remember working with IAsyncResult interface or working with callbacks? Thanks to Microsoft, today we have async and await keywords. I have bought O’Reilly book: “Async in C# 5.0″ by Alex Davies which is very good book and you can buy it here: O'Reilly Online Shop

In that book I found TAP (Task Async Pattern) pattern for writing asynchronous methods with async and await keywords. It is very interesting because the code is small and clear. You have few rules that you must follow in order for your method to work as asynchronous but that is not the problem.

Let’s get back to the Windows Forms application. I have created a few tables, done some relationship and other stuff that comes with the job. I have done that with DataSet because I am too lazy to write all those SQL queries that I can write in few lines with DataSet. DataSet is a cool stuff but not everything can be done with DataSet. In some cases, we have to write our own queries.

I have created a class that represents working with all those tables in the database and put methods for every single table (CRUD). And, it wouldn't be me if I didn't convert those synchronous methods into asynchronous type. One of the reason is that what if I call some method in the loop for about 500 times? That would be very slow for the application and Main/UI Thread would be blocked and user could not do anything in the application until that method is completed (could take a while). Just imagine how user could be frustrated if he could not do anything in the application for few seconds (or few minutes. It depends on how big data you put in the table is). Asynchronous type of that method will create a new thread for us and release Main/UI thread from being blocked/freezed. That is awesome. Since I have a habit to work with TAP pattern, I have worked with it again. Simple as it is, here is the pattern:

public async Task<T> MyMethodAsync()
{
//write parameters in the method if you have to
return await Task.Run(()=>MyMethod());
}

Very simple, isn't it?

To get the result of that asynchronous method in some other part of the code just write this:

var result = MyMethodAsync().Result;

(I put var type because we have T instead of data type)

When you are working with console type of applications, this would be perfect. But when you are working with GUI/ASP type of application, this would cause you a deadlock. This is the first time that I have experienced a deadlock. What is that? If you have GUI type of application (just like Windows Forms) and you are using TAP pattern like me, when you run that method, application will freeze. Not for a second, not for a minute. You can’t do anything but go to task manager and end your application as a task. What now? I got crazy because this didn't make any sense. Asynchronous method are those methods that are supposed to stop freezing the application, but my application became frozen.

ConfigureAwait

I’ve done some research and found on this nice site: MSDN Magazine > Best Practices in Asynchronous Programming, a part named “Configuring Context”. As you can see, the author of that article used one single method to avoid deadlock. And that is ConfigureAwait.

Just put that method as a part of Task line (Task.Run()…):

public async Task<T> MyMethodAsync()
{
return await Task.Run(()=>MyMethod()).ConfigureAwait(continueOnCapturedContext:  false);
}

And you are good to go.

What is ConfigureAwait method?

Well, ConfigureAwait method is used to configure an awaiter of Task on which we are using configuration. And the parameter "continueOnCapturedContext" is of boolean type and it must be set to true if we want to attempt to marshal the continuation back to the original context captured, otherwise, we will use false.

After I used that, everything was working as I have imagined and my tables got filled with data.

References