Table of contents

Is HttpClient (C#) thread-safe in C#?

Yes, HttpClient in C# is thread-safe and designed for concurrent use across multiple threads. Microsoft's official guidance recommends using a single HttpClient instance throughout your application's lifetime rather than creating new instances per request.

Why HttpClient Should Be Shared

Each HttpClient instance maintains its own connection pool. Creating multiple instances can cause:

  • Socket exhaustion due to excessive open connections
  • Poor performance from connection overhead
  • Resource waste and potential memory leaks

Thread-Safe Operations

The following HttpClient operations are thread-safe: - GetAsync(), PostAsync(), PutAsync(), DeleteAsync() - SendAsync() with different HttpRequestMessage instances - Reading configuration properties (BaseAddress, Timeout, etc.)

Basic Thread-Safe Usage

using System;
using System.Net.Http;
using System.Threading.Tasks;

public class HttpService
{
    // Shared instance across the application
    private static readonly HttpClient _httpClient = new HttpClient();

    public async Task<string> GetDataAsync(string url)
    {
        try
        {
            using var response = await _httpClient.GetAsync(url);
            response.EnsureSuccessStatusCode();
            return await response.Content.ReadAsStringAsync();
        }
        catch (HttpRequestException ex)
        {
            throw new InvalidOperationException($"Request failed: {ex.Message}", ex);
        }
    }
}

Concurrent Requests Example

public class ConcurrentHttpExample
{
    private static readonly HttpClient _client = new HttpClient();

    public async Task<string[]> FetchMultipleUrlsAsync(string[] urls)
    {
        // Create concurrent tasks
        var tasks = urls.Select(url => _client.GetStringAsync(url)).ToArray();

        // Wait for all requests to complete
        return await Task.WhenAll(tasks);
    }
}

// Usage
class Program
{
    static async Task Main(string[] args)
    {
        var httpExample = new ConcurrentHttpExample();
        var urls = new[]
        {
            "https://api.example.com/users",
            "https://api.example.com/posts",
            "https://api.example.com/comments"
        };

        var results = await httpExample.FetchMultipleUrlsAsync(urls);

        for (int i = 0; i < urls.Length; i++)
        {
            Console.WriteLine($"Response from {urls[i]}: {results[i].Length} characters");
        }
    }
}

Modern Best Practice: Dependency Injection

For .NET applications, use IHttpClientFactory for better lifecycle management:

// Startup.cs or Program.cs
services.AddHttpClient();

// Service class
public class ApiService
{
    private readonly HttpClient _httpClient;

    public ApiService(HttpClient httpClient)
    {
        _httpClient = httpClient;
    }

    public async Task<T> GetAsync<T>(string endpoint)
    {
        var response = await _httpClient.GetAsync(endpoint);
        response.EnsureSuccessStatusCode();

        var json = await response.Content.ReadAsStringAsync();
        return JsonSerializer.Deserialize<T>(json);
    }
}

Named Clients for Different APIs

// Configuration
services.AddHttpClient("GitHub", client =>
{
    client.BaseAddress = new Uri("https://api.github.com/");
    client.DefaultRequestHeaders.Add("User-Agent", "MyApp/1.0");
});

services.AddHttpClient("JsonPlaceholder", client =>
{
    client.BaseAddress = new Uri("https://jsonplaceholder.typicode.com/");
});

// Usage
public class MultiApiService
{
    private readonly IHttpClientFactory _clientFactory;

    public MultiApiService(IHttpClientFactory clientFactory)
    {
        _clientFactory = clientFactory;
    }

    public async Task<string> GetGitHubUserAsync(string username)
    {
        var client = _clientFactory.CreateClient("GitHub");
        return await client.GetStringAsync($"users/{username}");
    }

    public async Task<string> GetPostAsync(int postId)
    {
        var client = _clientFactory.CreateClient("JsonPlaceholder");
        return await client.GetStringAsync($"posts/{postId}");
    }
}

Thread-Safety Limitations

While HttpClient itself is thread-safe, these objects are NOT thread-safe:

  • HttpRequestMessage: Create a new instance for each request
  • HttpResponseMessage: Don't share across threads
  • Default headers: Avoid modifying DefaultRequestHeaders after initialization
// ❌ NOT thread-safe
var request = new HttpRequestMessage(HttpMethod.Get, "https://api.example.com");
// Don't reuse this request object across multiple threads

// ✅ Thread-safe approach
public async Task<string> SafeRequestAsync(string url)
{
    // Create new request message for each call
    using var request = new HttpRequestMessage(HttpMethod.Get, url);
    using var response = await _httpClient.SendAsync(request);

    return await response.Content.ReadAsStringAsync();
}

Performance Considerations

  • Connection pooling: HttpClient reuses TCP connections automatically
  • DNS resolution: Consider setting ServicePointManager.DnsRefreshTimeout for long-running applications
  • Timeout configuration: Set appropriate timeouts to prevent hanging requests
private static readonly HttpClient _client = new HttpClient()
{
    Timeout = TimeSpan.FromSeconds(30)
};

By following these patterns, you can safely use HttpClient in multi-threaded applications while maximizing performance and avoiding common pitfalls.

Try WebScraping.AI for Your Web Scraping Needs

Looking for a powerful web scraping solution? WebScraping.AI provides an LLM-powered API that combines Chromium JavaScript rendering with rotating proxies for reliable data extraction.

Key Features:

  • AI-powered extraction: Ask questions about web pages or extract structured data fields
  • JavaScript rendering: Full Chromium browser support for dynamic content
  • Rotating proxies: Datacenter and residential proxies from multiple countries
  • Easy integration: Simple REST API with SDKs for Python, Ruby, PHP, and more
  • Reliable & scalable: Built for developers who need consistent results

Getting Started:

Get page content with AI analysis:

curl "https://api.webscraping.ai/ai/question?url=https://example.com&question=What is the main topic?&api_key=YOUR_API_KEY"

Extract structured data:

curl "https://api.webscraping.ai/ai/fields?url=https://example.com&fields[title]=Page title&fields[price]=Product price&api_key=YOUR_API_KEY"

Try in request builder

Related Questions

Get Started Now

WebScraping.AI provides rotating proxies, Chromium rendering and built-in HTML parser for web scraping
Icon