Fixing the Sync over Async antipattern

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 fetches the weather for a city from a weather API. It displays the received JSON in a textbox.

Weather App that fetches weather data asynchronously

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; } }

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)
  • Change the caller to use async/await.
private async void btnGetWeather_Click(object sender, EventArgs e) { txtWeather.Text = await GetWeatherData(txtCity.Text); }

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);
  • 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();

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();

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(); }

Leave a Comment