Fixing the Sync over Async Antipattern in C#
By FoxLearn 1/21/2025 7:51:41 AM 14
The Sync over Async antipattern happens when blocking calls are used, such as Wait()
or Result
, on asynchronous methods. These methods are designed to be non-blocking, but when developers use blocking calls, they end up blocking the thread until the task completes, which can be detrimental to both performance and responsiveness, especially in user interface (UI) applications.
The two most common causes of this antipattern are:
- Calling
Wait()
on theTask
returned by an asynchronous method. - Using
Task.Result
, which also causes a blocking wait.
This article explores an example of the Sync over Async antipattern and provides a solution to fix it by converting the blocking calls into proper asynchronous code using await
.
To illustrate this issue, let's consider a simple weather application that fetches weather data from an API asynchronously using HttpClient
. However, the current implementation incorrectly blocks the thread with Wait()
and Result
calls.
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(); // Blocking call responseTask.Result.EnsureSuccessStatusCode(); var contentTask = responseTask.Result.Content.ReadAsStringAsync(); string responseData = contentTask.Result; // Another blocking call return responseData; } }
In this code:
responseTask.Wait()
blocks the calling thread until the HTTP request completes.contentTask.Result
also blocks until the content is read from the HTTP response.
These blocking calls can lead to UI freezing and poor performance.
Fixing the Sync over Async Antipattern
To fix this, we need to convert the method GetWeatherData
into an asynchronous method and use await
instead of blocking calls.
Convert GetWeatherData to Async
The first step is to change the GetWeatherData
method to return a Task<string>
and make it asynchronous. This allows us to use await
within the method.
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); // Use await httpResponse.EnsureSuccessStatusCode(); return await httpResponse.Content.ReadAsStringAsync(); // Use await }
Update the Caller to Use Async/Await
The event handler btnGetWeather_Click
also needs to be updated to use await
. Note that in UI event handlers, it is acceptable to use async void
.
private async void btnGetWeather_Click(object sender, EventArgs e) { txtWeather.Text = await GetWeatherData(txtCity.Text); // Use await }
Remove Blocking Waits
Now that the method is asynchronous, we can remove the blocking calls to Wait()
and Result
. By awaiting the tasks, we ensure that the thread is not blocked, and the method returns a result once the task is complete.
Here is the final, corrected version of the code after fixing the Sync over Async antipattern:
private async void btnGetWeather_Click(object sender, EventArgs e) { txtWeather.Text = await GetWeatherData(txtCity.Text); // Use await } 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); // Use await httpResponse.EnsureSuccessStatusCode(); return await httpResponse.Content.ReadAsStringAsync(); // Use await }
By using async
and await
, the code is more efficient and responsive:
- No blocking: The main thread is not blocked waiting for the task to complete.
- Simpler code: Using
await
eliminates the need forWait()
andResult
, simplifying the code. - Better performance: This ensures that your app does not freeze or become unresponsive, especially in UI-based applications.
The Sync over Async antipattern is a common issue in asynchronous programming, but it’s easily fixable by using await
instead of Wait()
and Result
.
- How to validate an IP address in C#
- How to retrieve the Downloads Directory Path in C#
- C# Tutorial
- How to convert a dictionary to a list in C#
- Dictionary with multiple values per key in C#
- How to start, stop and verify if a service exists in C#
- How to unit test async methods in C#
- C# Async/await with a Func delegate