The Sync over Async antipattern is when you’re using a blocking wait on an async method, instead of awaiting the results asynchronously.
This wastes the thread, causes unresponsiveness (if called from the UI), and exposes you to potential deadlocks.
There are two causes:
- Calling Wait() on the Task returned by the async call.
- Using Task.Result. This actually causes a blocking wait.
In this article I’ll show an example of the Sync over Async antipattern and how to fix it.
Antipattern: Sync over Async.
Definition: Using blocking waits when calling async methods.
Solution: Change the code to await the Task returned by the async call.
Sync over Async antipattern example
To show this problem I’ve created a simple app that uses HttpClient to get JSON from weather API (showing weather for a specific city) and then display it:
This code shows the two different causes of the Sync over Async antipattern. It’s calling Wait() on the Task returned by GetAsync(), and using .Result from the Task returned by ReadAsStringAsync().
public partial class frmWeather : Form
{
private readonly string API_KEY = "<blanked out>";
private readonly HttpClient httpClient = new HttpClient();
public frmWeather()
{
InitializeComponent();
}
private void btnGetWeather_Click(object sender, EventArgs e)
{
txtWeather.Text = GetWeatherData(txtCity.Text);
}
public string GetWeatherData(string City)
{
var url = $"http://api.openweathermap.org/data/2.5/weather?q={City}&units=imperial&APPID={API_KEY}";
var responseTask = httpClient.GetAsync(url);
responseTask.Wait();
responseTask.Result.EnsureSuccessStatusCode();
var contentTask = responseTask.Result.Content.ReadAsStringAsync();
string responseData = contentTask.Result;
return responseData;
}
}
Code language: C# (cs)
Convert GetWeatherData() to async
In order to fix the code, we’ll need to await the Tasks returned by the async methods. Before we can do that, we’ll need to convert the method to async.
- Change the method signature to async Task<string>.
public async Task<string> GetWeatherData(string City)
Code language: C# (cs)
- Change the caller to use async/await.
private async void btnGetWeather_Click(object sender, EventArgs e)
{
txtWeather.Text = await GetWeatherData(txtCity.Text);
}
Code language: C# (cs)
Note: Event handlers are the only exception to the rule about not using async void.
Await GetAsync()
Now that the GetWeatherData() method is async, we can call await on GetAsync().
- Add await before GetAsync().
- By awaiting GetAsync() we are no longer getting back a Task, but the result of the Task – an HttpResponse object. So let’s rename responseTask to httpResponse.
var httpResponse = await httpClient.GetAsync(url);
Code language: C# (cs)
- HttpResponse doesn’t have a .Result property, so we’ll get a few compiler errors. We can lean on the compiler to find the errors to correct. In this case, we need to change the code to use EnsureSuccessStatusCode() and .Content directly.
var httpResponse = await httpClient.GetAsync(url);
httpResponse.EnsureSuccessStatusCode();
var contentTask = httpResponse.Content.ReadAsStringAsync();
Code language: C# (cs)
Await ReadAsStringAsync()
The code is using .Result on the Task returned by ReadAsStringAsync(). This causes a blocking wait. This mistake is easier to make, because it’s not obvious that calling .Result would result in a blocking wait.
- Add await before ReadAsStringAsync() and return it.
return await httpResponse.Content.ReadAsStringAsync();
Code language: C# (cs)
Previously there were three lines of code. Changing this to use await reduced it down to one single line. This shows one of the benefits of using async/await – it simplifies the code.
Final fixed code
We fixed the Sync over Async antipattern by changing the code to await the Tasks returned by the async calls. We got rid of the explicit calls to .Wait() and .Result.
private async void btnGetWeather_Click(object sender, EventArgs e)
{
txtWeather.Text = await GetWeatherData(txtCity.Text);
}
public async Task<string> GetWeatherData(string City)
{
var url = $"http://api.openweathermap.org/data/2.5/weather?q={City}&units=imperial&APPID={API_KEY}";
var httpResponse = await httpClient.GetAsync(url);
httpResponse.EnsureSuccessStatusCode();
return await httpResponse.Content.ReadAsStringAsync();
}
Code language: C# (cs)
Note: If you have existing unit tests for the methods that you changed to async, be sure to read about how to unit test async methods.
Comments are closed.