Async / await in C #: pitfalls
- Transfer
I would like to discuss the pitfalls that are most common when working with async / await features in C #, and also write about how to get around them.
The async / await internals are well described by Alex Davis in his book , so I will only briefly describe them here. Consider the following code example:
This function reads one first byte from two files, the paths to which are passed through the parameters. What will happen on lines “1” and “2”? Will they be executed in parallel? Not. This function will be “broken” by the “await” keyword into three parts: the part preceding “1”, the part between “1” and “2” and the part following “2”.
The function will start a new I / O bound stream in line “1”, transfer the second part of itself (the part between “1” and “2”) to it as a callback, and return control. After the I / O thread completes, a callback will be called and the method will continue to execute. The method will create another I / O stream in line “2”, give it the third part of itself as a callback, and again return control. After the second I / O thread completes execution, the rest of the method will be launched.
The magic is present thanks to the compiler, which converts the methods marked with the keyword “async” into a state machine, similar to how it converts iterator methods .
There are two main scenarios in which using async / await is preferable.
First of all, this feature can be used in thick clients to provide users with a better user experience. When the user presses the button, starting a heavy computing operation, the best way out is to perform this operation asynchronously, without blocking the UI stream. Prior to .NET 4.5, such logic required much more effort. Now it can be programmed like this:
Note that the Enabled flag in both cases is set by the UI thread. This approach eliminates the need to write such ugly code:
In other words, all “light” code is executed by the calling thread, while the “heavy” parts are delegated to a separate thread (I / O or CPU-bound). This approach can significantly reduce the amount of effort required to synchronize access to UI elements, because they are managed only from the UI thread.
Secondly, async / await can be used in web applications to better utilize threads. The ASP.NET MVC team has made asynchronous controllers very easy to implement. You can simply write an action method as in the example below and ASP.NET will do the rest of the work:
In this example, the worker thread executing the method starts a new I / O thread on line “1” and returns to the thread pool. After the I / O thread completes, the CLR selects a new thread from the pool and it continues to execute the method. Thus, CPU-bound threads from a thread pool are used much more economically.
If you are developing a third-party library, it is very important to always configure await so that the rest of the method is executed by an arbitrary thread from the pool. In other words, in the code of third-party libraries you always need to add ConfigureAwait (false).
First of all, third-party libraries usually do not work with UI controls (unless of course it is not a UI library), so there is no need to bind a UI stream. You can increase performance a bit by letting the CLR execute your code with any thread in the pool. Secondly, using the default implementation (or explicitly affixing ConfigureAwait (true)), you leave a potential hole for deadlocks. Consider the following example:
Clicking on the button here leads to the deadlock. UI thread starts a new I / O stream on line "2" and goes into sleep mode on line "1", waiting for the completion of work. After the I / O thread finishes executing, the rest of the DoSomeWorkAsync method is passed to the calling (UI) thread for execution. But that one is in sleep mode at this time, waiting for the completion of the method. Deadlock.
ASP.NET behaves in the same way. Despite the fact that ASP.NET does not have a dedicated UI thread, the code in the actions of the controllers cannot be executed by more than one worker thread at a time.
Of course, we can use await instead of accessing the Result property in order to avoid deadlock:
But in .NET, there is still at least one case in which you cannot get around the deadlock. You cannot use asynchronous methods inside ASP.NET MVC child actions, as they are not supported. Thus, you will have to access the Result property directly and if the asynchronous method that is called by your controller is not configured correctly, you will get a deadlock. For example, if you try to write the following code and SomeAction refers to the Result property of an asynchronous method that has not been configured through ConfigureAwait (false), you again get a deadlock:
Users of your libraries usually do not have direct access to the code of these libraries, so always put ConfigureAwait (false) in your asynchronous methods in advance.
Consider an example:
Is this code executed asynchronously? Yes. Is this code a valid example of writing asynchronous code? Not. The UI thread here starts a new CPU-bound thread on line “1” and returns control. This thread then starts a new I / O stream on line “2” and goes into sleep mode, waiting for execution.
What is going on here? Instead of creating a single I / O stream, we create both a CPU stream on line “1” and an I / O stream on line “2”. This is a waste of threads. To fix this, we need to use the asynchronous version of the Read method:
One more example:
It looks like we're sending requests in parallel, right? Yes, it is, but here we have the same problem as in the previous example: instead of creating a single I / O stream, we create both an I / O and a CPU-bound stream for each request. You can fix the situation using the Task.WaitAll method:
Depends on the situation. In some cases this is not possible, in some it brings too much complexity to the code. For example, in NHibernate there is no possibility of asynchronously loading data from a database. On the other hand, it is in EntityFramework, but using it does not always make sense.
Also, thick clients (for example, WPF or WinForms applications) usually do not have large loads, so for them, such optimization is for the most part not necessary. But in any case, you need to know what is happening “under the hood” of this feature in order to be able to make a conscious decision in each particular case.
Link to the original article: Async / await in C #: pitfalls
How async / await works
The async / await internals are well described by Alex Davis in his book , so I will only briefly describe them here. Consider the following code example:
public async Task ReadFirstBytesAsync(string filePath1, string filePath2)
{
using (FileStream fs1 = new FileStream(filePath1, FileMode.Open))
using (FileStream fs2 = new FileStream(filePath2, FileMode.Open))
{
await fs1.ReadAsync(new byte[1], 0, 1); // 1
await fs2.ReadAsync(new byte[1], 0, 1); // 2
}
}
This function reads one first byte from two files, the paths to which are passed through the parameters. What will happen on lines “1” and “2”? Will they be executed in parallel? Not. This function will be “broken” by the “await” keyword into three parts: the part preceding “1”, the part between “1” and “2” and the part following “2”.
The function will start a new I / O bound stream in line “1”, transfer the second part of itself (the part between “1” and “2”) to it as a callback, and return control. After the I / O thread completes, a callback will be called and the method will continue to execute. The method will create another I / O stream in line “2”, give it the third part of itself as a callback, and again return control. After the second I / O thread completes execution, the rest of the method will be launched.
The magic is present thanks to the compiler, which converts the methods marked with the keyword “async” into a state machine, similar to how it converts iterator methods .
When to use async / await?
There are two main scenarios in which using async / await is preferable.
First of all, this feature can be used in thick clients to provide users with a better user experience. When the user presses the button, starting a heavy computing operation, the best way out is to perform this operation asynchronously, without blocking the UI stream. Prior to .NET 4.5, such logic required much more effort. Now it can be programmed like this:
private async void btnRead_Click(object sender, EventArgs e)
{
btnRead.Enabled = false;
using (FileStream fs = new FileStream(“File path”, FileMode.Open))
using (StreamReader sr = new StreamReader(fs))
{
Content = await sr.ReadToEndAsync();
}
btnRead.Enabled = true;
}
Note that the Enabled flag in both cases is set by the UI thread. This approach eliminates the need to write such ugly code:
if (btnRead.InvokeRequired)
{
btnRead.Invoke((Action)(() => btnRead.Enabled = false));
}
else
{
btnRead.Enabled = false;
}
In other words, all “light” code is executed by the calling thread, while the “heavy” parts are delegated to a separate thread (I / O or CPU-bound). This approach can significantly reduce the amount of effort required to synchronize access to UI elements, because they are managed only from the UI thread.
Secondly, async / await can be used in web applications to better utilize threads. The ASP.NET MVC team has made asynchronous controllers very easy to implement. You can simply write an action method as in the example below and ASP.NET will do the rest of the work:
public class HomeController : Controller
{
public async Task Index()
{
using (FileStream fs = new FileStream(“File path”, FileMode.Open))
using (StreamReader sr = new StreamReader(fs))
{
return await sr.ReadToEndAsync(); // 1
}
}
}
In this example, the worker thread executing the method starts a new I / O thread on line “1” and returns to the thread pool. After the I / O thread completes, the CLR selects a new thread from the pool and it continues to execute the method. Thus, CPU-bound threads from a thread pool are used much more economically.
Async / await in C #: pitfalls
If you are developing a third-party library, it is very important to always configure await so that the rest of the method is executed by an arbitrary thread from the pool. In other words, in the code of third-party libraries you always need to add ConfigureAwait (false).
First of all, third-party libraries usually do not work with UI controls (unless of course it is not a UI library), so there is no need to bind a UI stream. You can increase performance a bit by letting the CLR execute your code with any thread in the pool. Secondly, using the default implementation (or explicitly affixing ConfigureAwait (true)), you leave a potential hole for deadlocks. Consider the following example:
private async void button1_Click(object sender, EventArgs e)
{
int result = DoSomeWorkAsync().Result; // 1
}
private async Task DoSomeWorkAsync()
{
await Task.Delay(100).ConfigureAwait(true); //2
return 1;
}
Clicking on the button here leads to the deadlock. UI thread starts a new I / O stream on line "2" and goes into sleep mode on line "1", waiting for the completion of work. After the I / O thread finishes executing, the rest of the DoSomeWorkAsync method is passed to the calling (UI) thread for execution. But that one is in sleep mode at this time, waiting for the completion of the method. Deadlock.
ASP.NET behaves in the same way. Despite the fact that ASP.NET does not have a dedicated UI thread, the code in the actions of the controllers cannot be executed by more than one worker thread at a time.
Of course, we can use await instead of accessing the Result property in order to avoid deadlock:
private async void button1_Click(object sender, EventArgs e)
{
int result = await DoSomeWorkAsync();
}
private async Task DoSomeWorkAsync()
{
await Task.Delay(100).ConfigureAwait(true);
return 1;
}
But in .NET, there is still at least one case in which you cannot get around the deadlock. You cannot use asynchronous methods inside ASP.NET MVC child actions, as they are not supported. Thus, you will have to access the Result property directly and if the asynchronous method that is called by your controller is not configured correctly, you will get a deadlock. For example, if you try to write the following code and SomeAction refers to the Result property of an asynchronous method that has not been configured through ConfigureAwait (false), you again get a deadlock:
@Html.Action(“SomeAction“, “SomeController“)
Users of your libraries usually do not have direct access to the code of these libraries, so always put ConfigureAwait (false) in your asynchronous methods in advance.
How not to use PLINQ and async / await
Consider an example:
private async void button1_Click(object sender, EventArgs e)
{
btnRead.Enabled = false;
string content = await ReadFileAsync();
btnRead.Enabled = true;
}
private Task ReadFileAsync()
{
return Task.Run(() => // 1
{
using (FileStream fs = new FileStream(“File path”, FileMode.Open))
using (StreamReader sr = new StreamReader(fs))
{
return sr.ReadToEnd(); // 2
}
});
}
Is this code executed asynchronously? Yes. Is this code a valid example of writing asynchronous code? Not. The UI thread here starts a new CPU-bound thread on line “1” and returns control. This thread then starts a new I / O stream on line “2” and goes into sleep mode, waiting for execution.
What is going on here? Instead of creating a single I / O stream, we create both a CPU stream on line “1” and an I / O stream on line “2”. This is a waste of threads. To fix this, we need to use the asynchronous version of the Read method:
private Task ReadFileAsync()
{
using (FileStream fs = new FileStream(“File path”, FileMode.Open))
using (StreamReader sr = new StreamReader(fs))
{
return sr.ReadToEndAsync();
}
}
One more example:
public void SendRequests()
{
_urls.AsParallel().ForAll(url =>
{
var httpClient = new HttpClient();
httpClient.PostAsync(url, new StringContent(“Some data”));
});
}
It looks like we're sending requests in parallel, right? Yes, it is, but here we have the same problem as in the previous example: instead of creating a single I / O stream, we create both an I / O and a CPU-bound stream for each request. You can fix the situation using the Task.WaitAll method:
public void SendRequests()
{
IEnumerable tasks = _urls.Select(url =>
{
var httpClient = new HttpClient();
return httpClient.PostAsync(url, new StringContent(“Some data”));
});
Task.WaitAll(tasks.ToArray());
}
Is it always necessary to perform I / O operations without binding CPU-bound threads?
Depends on the situation. In some cases this is not possible, in some it brings too much complexity to the code. For example, in NHibernate there is no possibility of asynchronously loading data from a database. On the other hand, it is in EntityFramework, but using it does not always make sense.
Also, thick clients (for example, WPF or WinForms applications) usually do not have large loads, so for them, such optimization is for the most part not necessary. But in any case, you need to know what is happening “under the hood” of this feature in order to be able to make a conscious decision in each particular case.
Link to the original article: Async / await in C #: pitfalls