HttpClient - 任务被取消?

问题描述 投票:0回答:9

当有一个或两个任务时它工作正常,但是当我们列出多个任务时会抛出错误“任务被取消”。

enter image description here

List<Task> allTasks = new List<Task>();
allTasks.Add(....);
allTasks.Add(....);
Task.WaitAll(allTasks.ToArray(), configuration.CancellationToken);


private static Task<T> HttpClientSendAsync<T>(string url, object data, HttpMethod method, string contentType, CancellationToken token)
{
    HttpRequestMessage httpRequestMessage = new HttpRequestMessage(method, url);
    HttpClient httpClient = new HttpClient();
    httpClient.Timeout = new TimeSpan(Constants.TimeOut);

    if (data != null)
    {
        byte[] byteArray = Encoding.ASCII.GetBytes(Helper.ToJSON(data));
        MemoryStream memoryStream = new MemoryStream(byteArray);
        httpRequestMessage.Content = new StringContent(new StreamReader(memoryStream).ReadToEnd(), Encoding.UTF8, contentType);
    }

    return httpClient.SendAsync(httpRequestMessage).ContinueWith(task =>
    {
        var response = task.Result;
        return response.Content.ReadAsStringAsync().ContinueWith(stringTask =>
        {
            var json = stringTask.Result;
            return Helper.FromJSON<T>(json);
        });
    }).Unwrap();
}
c# task-parallel-library dotnet-httpclient
9个回答
379
投票

抛出

TaskCanceledException
的可能原因有 2 个:

  1. 在任务完成之前,与取消令牌关联的
    Cancel()
    上名为
    CancellationTokenSource
    的东西。
  2. 请求超时,即未在您指定的时间范围内完成
    HttpClient.Timeout

我的猜测是超时。 (如果是显式取消,您可能已经弄清楚了。)您可以通过检查异常来更加确定:

try
{
    var response = task.Result;
}
catch (TaskCanceledException ex)
{
    // Check ex.CancellationToken.IsCancellationRequested here.
    // If false, it's pretty safe to assume it was a timeout.
}

30
投票

我遇到了这个问题,因为我的

Main()
方法在返回之前没有等待任务完成,因此当我的控制台程序退出时
Task<HttpResponseMessage>
被取消。

C#≥7.1

您可以使 main 方法异步并等待任务。

public static async Task Main(){
    Task<HttpResponseMessage> myTask = sendRequest(); // however you create the Task
    HttpResponseMessage response = await myTask;
    // process the response
}

C# < 7.1

解决方案是在

myTask.GetAwaiter().GetResult()
中调用
Main()
(来自这个答案)。


24
投票
var clientHttp = new HttpClient();
clientHttp.Timeout = TimeSpan.FromMinutes(30);

以上是等待大请求的最佳方法。 你困惑了大约30分钟;这是随机时间,您可以提供任何您想要的时间。

换句话说,如果请求在 30 分钟之前得到结果,则不会等待 30 分钟。 30 分钟表示请求处理时间为 30 分钟。 当我们出现错误“任务被取消”,或者大数据请求需求时。


17
投票

另一种可能是客户端没有等待结果。如果调用堆栈上的任何一个方法不使用await 关键字来等待调用完成,则可能会发生这种情况。


11
投票

将@JobaDiniz 的评论提升为答案:

不要做显而易见的事情并处置

HttpClient
实例,即使代码“看起来正确”:

async Task<HttpResponseMessage> Method() {
  using (var client = new HttpClient())
    return client.GetAsync(request);
}

处置

HttpClient
实例可能会导致由其他
HttpClient
实例启动的后续 HTTP 请求被取消!

C# 的新 RIAA 语法也会发生同样的情况;稍微不太明显:

async Task<HttpResponseMessage> Method() {
  using var client = new HttpClient();
  return client.GetAsync(request);
}

相反,

HttpClient
界面有点粗糙,在dotnet core 2.1+中使用它的正确方法是:

使用静态或单例

HttpClient
实例与
PooledConnectionLifetime
...

基本上,为您的应用程序或库缓存

HttpClient
的静态实例,并重用它:

static HttpClient client = new HttpClient();

async Task<HttpResponseMessage> Method() {
  return client.GetAsync(request);
}

Async()
请求方法都是线程安全的。)


但是,Microsoft 建议 您不使用的情况不要直接使用

new HttpClient()
对于 dotnet core 和 .NET Framework,请使用
IHttpClientFactory
。需要注意的是:
IHttpClientFactory
的文档通常适用于 ASP.NET 应用程序,但可以在任何 CLI/GUI 应用程序中使用它。)

(对于 .NET Framework,“您可以将 Microsoft.Extensions.Http NuGet 包添加到任何符合 .NET Standard 2.0 的项目”——请参阅 此答案。)


6
投票

在我的 .net core 3.1 应用程序中,我遇到两个问题,其中内部原因是超时异常。 1,一是我收到聚合异常,其内部异常是超时异常 2、其他情况是任务取消异常

我的解决方案是

catch (Exception ex)
            {
                if (ex.InnerException is TimeoutException)
                {
                    ex = ex.InnerException;
                }
                else if (ex is TaskCanceledException)
                {
                    if ((ex as TaskCanceledException).CancellationToken == null || (ex as TaskCanceledException).CancellationToken.IsCancellationRequested == false)
                    {
                        ex = new TimeoutException("Timeout occurred");
                    }
                }                
                Logger.Fatal(string.Format("Exception at calling {0} :{1}", url, ex.Message), ex);
            }

2
投票

在我的情况下,控制器方法没有设为异步,并且控制器方法内部调用的方法是异步的。

所以我认为一直使用 async/await 到顶层以避免此类问题很重要。


1
投票

我使用了一个简单的调用而不是

async
。一旦我添加了
await
并制定了方法
async
,它就开始正常工作了。

public async Task<T> ExecuteScalarAsync<T>(string query, object parameter = null, CommandType commandType = CommandType.Text) where T : IConvertible
        {
            using (IDbConnection db = new SqlConnection(_con))
            {
                return await db.ExecuteScalarAsync<T>(query, parameter, null, null, commandType);
            }
        }

0
投票

另一个原因可能是,如果您正在运行服务 (API) 并在服务中放置断点(并且您的代码卡在某个断点处(例如 Visual Studio 解决方案显示 Debugging 而不是 Running))。然后从客户端代码调用 API。因此,如果服务代码在某个断点处暂停,您只需在 VS 中按 F5 即可。

© www.soinside.com 2019 - 2024. All rights reserved.