HttpClient in using 语句导致任务取消

HttpClient in using statement causes Task cancelled

我为我的 api 调用创建了一个 FileResult : IHttpActionResult webapi return 类型。 FileResult 从另一个 url 下载文件,然后 return 将流传输到客户端。

最初我的代码有如下 using 语句:

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)
    {...}
}

然而,这会间歇性地导致 TaskCanceledException。我知道如果在异步调用完成之前处理 HttpClient,则任务的状态将更改为已取消。但是,由于我在 await 中使用了 Content = new System.Net.Http.StreamContent(await httpClient.GetStreamAsync(this.filePath)) ,这应该可以防止 HttpClient 在任务完成过程中被处理掉。

为什么那个任务被取消了?这不是因为超时,因为这发生在最小的请求上,并不总是发生在大的请求上。

当我删除 using 语句时,代码正常运行:

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)
    {...}
}

知道使用导致问题的原因吗?

我对任务取消异常有类似的问题。如果您尝试捕获 AggregateException 或在您的 WebException 下方设置捕获所有 Exception 块,您很可能会发现您捕获了它,但有一个例外,条目为 "A task was canceled"

我做了一些调查,发现 AggregateException 像各种线程中描述的那样具有误导性;

Bug in httpclientgetasync should throw webexception not taskcanceledexception

我最终更改了我的代码以设置明确的超时(其中 asyncTimeoutInMins 是从 app.config 文件中读取的);

        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;

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.

但是该任务 做什么? 它获取流。因此,您的代码以 Stream 结束,当它关闭 HttpClient.

时,它可能会或可能不会被完全读取

HttpClient 专为重用(和同时使用)而设计,因此我建议完全删除 using 并将 HttpClient 声明移动到 static class 成员。但是如果你想关闭并重新打开客户端,你应该能够通过在关闭 HttpClient.

之前 将流完全读入 到内存中来使其工作