- Home
- .NET tutorials
- How to use C# async/await for API calls & to stop blocking
How to use C# async/await for API calls & to stop blocking
Published: Monday 3 June 2024
Using the async and await keywords in C# helps prevents blocking of the main thread for intensive operations.
public Product GetProduct(int id)
{
return new Product
{
Id = 1,
Title = "Television",
Description = "55 inch TV",
Price = 699
};
}
If we wish to make this method asynchronous, we have to mark the method as async and return a Task type.
public async Task<Product> GetProduct(int id)
{
return new Product
{
Id = 1,
Title = "Television",
Description = "55 inch TV",
Price = 699
};
}
It's only worth marking a method asynchronous when we have make an asynchronous call within it.
The differences between synchronous and asynchronous
Let's just say that you are in a shop and there is one person assisting to the customers.
A queue where there are two awaiting people
This is fine as you are next in the queue and you shouldn't have to wait too long.
Five customers have now joined the queue
The customer who has joined the back of the queue is going to have to wait until the other four customers in front of them have been served before it's their turn.
Asynchronous
In this same situation, just say two more assistants come out and help serve the customers.
Three queues are now set up for the customers
All of a sudden, there are now three queues which are much shorter.
So does that mean I should always use asynchronous?
No it doesn't. Just say we had three assistances working, but only one person in the queue.
One person in a queue when there are three assistances
The other two assistants aren't doing anything meaning they are complete waste of resource.
When to use asynchronous
In our example, the assistant represents the thread, or main thread. Take this simple method:
public int Add(int a, int b)
{
return a + b;
}
It would be reasonable to expect that adding two numbers together would take your application no time at all to calculate.
- Making an API call
- Making a database call
- Uploading a file
One common factor with these tasks is that they have to make a call outside of the application. Therefore, network connection and traffic congestion can be reasons why we make these calls async as we don't know for sure when we are going to be returned with a response.
The problem with blocking threads
The problem with blocking threads is almost certainly going to be an issue in production when there are too many requests made at the same time.
The web is asynchronous by design
Just say a request to an API takes on average 500ms to process and you are in 50th position in the queue. You'd have to wait 24.5 seconds before your request is processed if it was synchronous.
How to use asynchronous on an API call
To make a HTTP call in ASP.NET Core, we create a HTTP client from the IHttpClientFactory instance.
HttpClient class, there is a GetAsync method that makes a HTTP GET request to the inputted endpoint and returns a response. This is an asynchronous method as it returns a type of Task.
await keyword before calling the method. This ensures that the compiler won't go onto the next line until we get a result.
public async Task<Product?> GetProduct(int id)
{
using var httpClient = _httpClientFactory.CreateClient("DummyJson");
var httpResponse = await httpClient.GetAsync($"/products/{id}");
if (httpResponse == null)
{
return null;
}
httpResponse.EnsureSuccessStatusCode();
return await httpResponse.Content.ReadFromJsonAsync<Product>();
}
If we didn't use the await keyword, the application will continue executing the method whilst the HTTP GET request is running in the background. There is a good chance that the method would be returned before the HTTP GET request has finished. Therefore, we couldn't return the data that the method is set out to do.
Other Task operations
One of the good things about using asynchronous programming is that we can set off tasks and await for some, or all of them to complete meaning they can all run in the background.
GetProduct method in an API controller. However, we want to return three products, so we can start to run each of the three requests.
Task.WhenAll method passing in each of the three tasks as parameters. The application will only return the result when we have a response for all three methods.
[ApiController]
[Route("api/http")]
public class HttpController : Controller
{
private readonly IHttpService _httpService;
public HttpController(IHttpService httpService)
{
_httpService = httpService;
}
[HttpGet("products")]
public async Task<IActionResult> GetProducts()
{
var firstProduct = _httpService.GetProduct(1);
var secondProduct = _httpService.GetProduct(2);
var thirdProduct = _httpService.GetProduct(3);
await Task.WhenAll(firstProduct, secondProduct, thirdProduct);
return Ok(new
{
FirstProduct = firstProduct.Result,
SecondProduct = secondProduct.Result,
ThirdProduct = thirdProduct.Result
});
}
}
Other Task operations include Task.WhenAny which is similar to Task.WhenAll, but the application will continue when any of the tasks are turned.
Task.Delay allows you to add a delay to the application without blocking a thread which is a much better alternative to using Thread.Sleep.
[HttpGet("products")]
public async Task<IActionResult> GetProducts()
{
var firstProduct = _httpService.GetProduct(1);
var secondProduct = _httpService.GetProduct(2);
var thirdProduct = _httpService.GetProduct(3);
await Task.Delay(TimeSpan.FromMilliseconds(300)); // Adds a 300ms delay
await Task.WhenAll(firstProduct, secondProduct, thirdProduct);
return Ok(new
{
FirstProduct = firstProduct.Result,
SecondProduct = secondProduct.Result,
ThirdProduct = thirdProduct.Result
});
}
There is also Task.Run. This is similar to writing an asynchronous method and returning it as a Task, but it can be written inline.
[ApiController]
[Route("api/http")]
public class HttpController : Controller
{
private readonly IHttpService _httpService;
public HttpController(IHttpService httpService)
{
_httpService = httpService;
}
[HttpGet("products")]
public async Task<IActionResult> GetProducts()
{
var firstProduct = _httpService.GetProduct(1);
var secondProduct = _httpService.GetProduct(2);
var thirdProduct = _httpService.GetProduct(3);
var abc = Task.Run(() =>
{
// My TASK HERE
});
await Task.WhenAll(firstProduct, secondProduct, thirdProduct, abc);
return Ok(new
{
FirstProduct = firstProduct.Result,
SecondProduct = secondProduct.Result,
ThirdProduct = thirdProduct.Result
});
}
}
Watch the video
Watch the video where we show you the difference between synchronous and asynchronous calls and how to use them in an ASP.NET Core Web API.
Latest tutorials
File logging in ASP.NET Core made easy with Serilog
Learn how to add file logging to an ASP.NET Core app using Serilog, including setup, configuration, and writing logs to daily rolling files.