In this article, we’ll demonstrate how to set a default request timeout for all requests and configure specific request timeout settings for individual requests using HttpClient.
In modern software development, managing HTTP requests efficiently is vital for keeping our applications fast and reliable. The HttpClient enables us to effectively execute HTTP calls in .NET applications. Properly managing timeouts is essential to prevent delays caused by slow network responses.
Let’s start.
Understanding HttpClient Request Timeout
By default, HttpClient
in .NET times out after 100 seconds, raising a TaskCanceledException
if no response is received. Adjusting this timeout is key to enhancing performance in fast operations and extending wait times for longer processes. So, properly managing this setting improves user experience, application reliability, and responsiveness, ensuring efficiency under various network conditions.
Now, let’s move on to explore some practical scenarios.
Set a Default (Global) Request Timeout
The global timeout for an HttpClient
instance affects all requests and is set through the Timeout
property. Developers usually retrieve this value from the application’s configuration, making adjustments easier. Setting a consistent global timeout balances responsiveness with sufficient processing time for requests under different network conditions.
Let’s consider an example where we configure a global timeout in appsettings.json
:
{ "TestClient": { "TimeOutSeconds": 3 } }
Next, we apply this timeout to HttpClient
:
builder.Services.AddHttpClient("TestClient", (sp, httpClient) => { var configuration = sp.GetRequiredService<IConfiguration>(); var timeoutSeconds = configuration.GetValue<int>("TestClient:TimeOutSeconds"); httpClient.Timeout = TimeSpan.FromSeconds(timeoutSeconds); });
We read the timeout setting from the configuration
provided by the service provider. Consequently, this configuration ensures all requests via this configured HttpClient
resolved with the name TestClient
maintain a uniform timeout of 3 seconds.
Let’s look at a scenario where we send a request to an external endpoint:
app.MapGet("/api/test-global-timeout", async (IHttpClientFactory httpClientFactory) => { var httpClient = httpClientFactory.CreateClient("TestClient"); try { var response = await httpClient.GetAsync("/api/delay-4-seconds"); return Results.Ok(); } catch (TaskCanceledException) { return Results.Text("TaskCanceledException: HttpClient global timeout passed"); } });
Here, we have an endpoint that responds in 4 seconds—1 second more than our configured global timeout.
Consequently, the HttpClient
throws TaskCanceledException
and cancels the request, illustrating the importance of aligning timeouts with expected response times.
Set a Per-Request Timeout
Moreover, while a well-adjusted global request timeout maintains our application’s responsiveness, setting per-request timeouts for each HTTP call enhances this responsiveness.
We establish these timeouts by either creating a custom cancellation token with a specified timeout or, in web applications, using the CancellationToken
from the controller’s action or, as in our example, minimal API. This token represents the HttpContext.Aborted
token from the incoming user’s request, providing an effective way to manage timeouts at the request level. Moreover, this approach proves particularly beneficial as it immediately halts the process if a client cancels their request, thus conserving server resources and time.
Now, let’s create another endpoint to test the per-request timeout:
app.MapGet( "/api/test-per-request-timeout", async (IHttpClientFactory httpClientFactory, CancellationToken cancellationToken) => { var httpClient = httpClientFactory.CreateClient("TestClient"); try { var response = await httpClient.GetAsync($"/api/delay-4-seconds", cancellationToken); return Results.Ok(); } catch (TaskCanceledException) { return Results.Text("TaskCanceledException: User request cancelled"); } } );
In this case, we use the HttpContext.Aborted
cancellationToken
, which cancels the operation if the user aborts the request.
We can use this method in conjunction with the global timeout, where the shorter duration between the two determines when the request is canceled. This strategy not only enhances responsiveness by swiftly freeing up resources but also allows detailed control over the server’s wait time, tailored to the specific needs of each request.
Implement Additional Request Timeout
Sometimes, even with a global timeout and a Context.Aborted
token, a particular request might need a dedicated timeout setting. This request-specific timeout offers more granular control over response times, independent of broader settings or client-side cancellations.
Let’s look at an example of how we can implement an additional combined timeout:
app.MapGet( "/api/test-combined-timeout", async (IHttpClientFactory httpClientFactory, CancellationToken cancellationToken) => { var httpClient = httpClientFactory.CreateClient("TestClient"); using var endpointSpecificToken = new CancellationTokenSource(TimeSpan.FromSeconds(2)); using var tokenSource = CancellationTokenSource.CreateLinkedTokenSource( cancellationToken, endpointSpecificToken.Token ); try { var response = await httpClient.GetAsync("/api/delay-4-seconds", tokenSource.Token); return Results.Ok(); } catch (TaskCanceledException) { return endpointSpecificToken.IsCancellationRequested ? Results.Text("TaskCanceledException: Specific token canceled") : Results.Text("TaskCanceledException: HttpClient global timeout passed"); } } );
Here, we use a linked token source tokenSource
combining the request’s cancellationToken
with a new, stricter 2-second timeout endpointSpecificToken
. The principle remains—the shortest of the three cancellation sources—global timeout, user’s cancellation token, and the specific token—will abort the operation.
Ultimately, this precise management of timeouts is tailored to the urgency of the request and it ensures efficient and timely operations.
Handling Timeouts Gracefully
In the examples we’ve covered, the catch blocks managing TaskCanceledException
stand out. Catching this exception during timeouts enables our application to respond effectively. We can either retry the operation or inform the user.
A common strategy among developers is to implement retry mechanisms using libraries like Polly, which provides a straightforward and efficient way to establish complex retry strategies and fallback methods.
Conclusion
In this article, we have learned that effectively managing timeouts in HttpClient is crucial for building responsive and reliable .NET applications. We accomplish this by carefully configuring both global and per-request timeouts. Additionally, by prioritizing graceful handling of timeouts we improve user experience and maintain the application’s performance.
Now, let’s return to the applications we are developing and implement what we’ve learned today.