Search code examples
c#asynchronousdotnet-httpclientsynchronous

Different results when using sync, async or async with shared HttpClient


Introduction

I've been working in ASP.NET Web API where I bumped into some weird interactions when trying to call another API. I've tried 3 different ways to use HttpClient, all with different results.
Have tested everything using Postman, hence some results.

1. Sync HttpClient call

private static string GetAPI(string url)
{
    using (HttpClient client = new HttpClient())
    {
        client.BaseAddress = new Uri(url);
        client.DefaultRequestHeaders.Accept.Clear();
        client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
        client.DefaultRequestHeaders.Add("x-api-key", "SomeSecretApiKey");

        HttpResponseMessage response = client.GetAsync(url).Result;
        string contents = response.Content.ReadAsStringAsync().Result;

        if (response.IsSuccessStatusCode)
        {
            return contents;
        }
    }

    return null;
}

Result

Does work, but I want to use async

2. Async httpClient call

private static async Task<string> GetAPI(string url)
{
    using (HttpClient client = new HttpClient())
    {
        client.BaseAddress = new Uri(url);
        client.DefaultRequestHeaders.Accept.Clear();
        client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
        client.DefaultRequestHeaders.Add("x-api-key", "SomeSecretApiKey");

        HttpResponseMessage response = await client.GetAsync(url);
        string contents = await response.Content.ReadAsStringAsync();

        if (response.IsSuccessStatusCode)
        {
            return contents;
        }
    }

    return null;
}

Result

Does not work. Will not go beyond the line HttpResponseMessage response = await client.GetAsync(url);, since there's never a response?

3. Async httpClient call with shared HttpClient

private static readonly HttpClient client = new HttpClient();

private static async Task<string> GetAPI(string url)
{
    client.BaseAddress = new Uri(url);
    client.DefaultRequestHeaders.Accept.Clear();
    client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
    client.DefaultRequestHeaders.Add("x-api-key", "SomeSecretApiKey");

    HttpResponseMessage response = await client.GetAsync(url);
    string contents = await response.Content.ReadAsStringAsync();

    if (response.IsSuccessStatusCode)
    {
        return contents;
    }

    return null;
}

Result

Does work once. Will then throw the error:

System.AggregateException: One or more errors occurred. ---> System.InvalidOperationException: This instance has already started one or more requests. Properties can only be modified before sending the first request.

(as suggested by this SO answer).

Question

I would appreciate it if someone could explain why these results are so different and/or give an alternative way to make a basic HttpClient call (still would like to use async).


Solution

  • @Igor has informed me about the deadlock issue in my second example.
    So I currently used the HttpClient as such (async):

    private static async Task<string> GetAPI(string url)
        {
        // TODO: Use a shared instance of HttpClient
        using (HttpClient client = new HttpClient())
        {
            client.BaseAddress = new Uri(url);
            client.DefaultRequestHeaders.Accept.Clear();
            client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
            client.DefaultRequestHeaders.Add("x-api-key", "SomeSecretApiKey");
    
            var jsonString = await client.GetStringAsync(url).ConfigureAwait(false);
            return jsonString;
        }
    }
    

    While some things have been made clear, the entire question hasn't been answered yet. I therefore won't accept this as an answer, yet like to thank those for providing me good information on the use of HttpClient.