简体   繁体   中英

HttpClient in using statement causes Task cancelled

I created a FileResult : IHttpActionResult webapi return type for my api calls. The FileResult downloads a file from another url and then returns the stream to the client.

Initially my code had a using statement like below:

public async Task<HttpResponseMessage> ExecuteAsync(CancellationToken cancellationToken)
{
    try
    {
        HttpResponseMessage response;
        using (var httpClient = new HttpClient())
        {

            response = new HttpResponseMessage(HttpStatusCode.OK)
            {
                Content = new System.Net.Http.StreamContent(
                                    await httpClient.GetStreamAsync(this.filePath))
            };
        }
        return response;
    }
    catch (WebException exception)
    {...}
}

However this would intermittently cause a TaskCanceledException . I know that if the HttpClient is disposed before the asychronous call is finished the Task's state will change to canceled. However since I use an await in: Content = new System.Net.Http.StreamContent(await httpClient.GetStreamAsync(this.filePath)) that should prevent the HttpClient from being disposed off in the middle of the task completion.

Why does that task get canceled? It is not because of a timeout since this has happened on the smallest requests and doesn't always occur on large requests.

When I removed the using statement the code worked properly:

public async Task<HttpResponseMessage> ExecuteAsync(CancellationToken cancellationToken)
{
    try
    {
        HttpResponseMessage response;
        var httpClient = new HttpClient();

        response = new HttpResponseMessage(HttpStatusCode.OK)
        {
            Content = new System.Net.Http.StreamContent(
                                await httpClient.GetStreamAsync(this.filePath))
        };
        return response;
    }
    catch (WebException exception)
    {...}
}

Any idea why the using caused the issue?

I know that if the HttpClient is disposed before the asychronous call is finished the Task's state will change to canceled. However since I use an await in: Content = new System.Net.Http.StreamContent(await httpClient.GetStreamAsync(this.filePath)) that should prevent the HttpClient from being disposed off in the middle of the task completion.

But what does that task do? It gets the stream. So, your code ends up with a Stream that may or may not be completely read when it closes the HttpClient .

HttpClient is specifically designed for reuse (and simultaneous use), so I recommend removing the using completely and moving the HttpClient declaration to a static class member. But if you want to close and reopen the clients, you should be able to get it working by reading the stream entirely into memory before closing the HttpClient .

I had a similar issue with Task Canceled exceptions. If you try catching AggregateException or having a catch all Exception block underneath your WebException , you may well find that you catch it, with one exception with the entry stating "A task was canceled"

I did some investigation and found that the AggregateException is quite misleading as described in various threads;

Setting HttpClient to a too short timeout crashes process

How can I tell when HttpClient has timed out?

Bug in httpclientgetasync should throw webexception not taskcanceledexception

I ended up changing my code to set an explicit timeout (where asyncTimeoutInMins is read from the app.config file);

        string jsonResponse = string.Empty;
        try
        {
            using (HttpClient httpClient = new HttpClient())
            {
                httpClient.BaseAddress = new Uri(Properties.Settings.Default.MyWebService);
                httpClient.DefaultRequestHeaders.Accept.Clear();
                httpClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
                httpClient.Timeout = new TimeSpan(0, asyncTimeoutInMins, 0);

                HttpResponseMessage response;

                response = await httpClient.GetAsync("/myservice/resource");

                // Check the response StatusCode
                if (response.IsSuccessStatusCode)
                {
                    // Read the content of the response into a string
                    jsonResponse = await response.Content.ReadAsStringAsync();
                }
                else if (response.StatusCode == HttpStatusCode.Forbidden)
                {
                    jsonResponse = await response.Content.ReadAsStringAsync();

                    Logger.Instance.Warning(new HttpRequestException(string.Format("The response StatusCode was {0} - {1}", response.StatusCode.ToString(), jsonResponse)));

                    Environment.Exit((int)ExitCodes.Unauthorised);
                }
                else
                {
                    jsonResponse = await response.Content.ReadAsStringAsync();

                    Logger.Instance.Warning(new HttpRequestException(string.Format("The response StatusCode was {0} - {1}", response.StatusCode.ToString(), jsonResponse)));

                    Environment.Exit((int)ExitCodes.ApplicationError);
                }
           }

        }
        catch (HttpRequestException reqEx)
        {
            Logger.Instance.Error(reqEx);

            Console.WriteLine("HttpRequestException : {0}", reqEx.InnerException.Message);

            Environment.Exit((int)ExitCodes.ApplicationError);
        }
        catch (Exception ex)
        {
            Logger.Instance.Error(ex);

            throw;
        }

        return jsonResponse;

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM