C# 调用HttpClient并从分页请求中获得相同的结果-是我还是服务?

C# 调用HttpClient并从分页请求中获得相同的结果-是我还是服务?,c#,asynchronous,httpclient,C#,Asynchronous,Httpclient,我正在向同一个URL发送五个HttpClient请求,但带有不同的页码参数。它们都是异步启动的,然后我等待它们全部使用Tasks.WaitAll()完成。我的请求使用System.Net.Http.HttpClient 这基本上可以正常工作,我得到五个不同的结果,大约99%的时间代表每页数据 但有时,我还没有深入分析,每次任务都会得到完全相同的回答。每个任务确实实例化了自己的HttpClient。当我重用一个客户端实例时,我遇到了这个问题。但自从我开始为每个呼叫实例化新客户机后,问题就消失了 我

我正在向同一个URL发送五个HttpClient请求,但带有不同的页码参数。它们都是异步启动的,然后我等待它们全部使用Tasks.WaitAll()完成。我的请求使用System.Net.Http.HttpClient

这基本上可以正常工作,我得到五个不同的结果,大约99%的时间代表每页数据

但有时,我还没有深入分析,每次任务都会得到完全相同的回答。每个任务确实实例化了自己的HttpClient。当我重用一个客户端实例时,我遇到了这个问题。但自从我开始为每个呼叫实例化新客户机后,问题就消失了

我正在呼叫一个我无法控制的第三方web服务。所以在对他们的团队唠叨太多之前,我确实想知道我是否在这里做错了什么,或者我是否遗漏了HttpClient任务的某些方面

以下是呼叫代码:

    for (int i = 1; i <= 5; i++)
    {
        page = load_made + i;
        var t_page = page;
        var t_url = url;
        var task = new Task<List<T>>(() => DoPagedLoad<T>(t_page, per_page, t_url));
        task.Run();
        tasks.Add(task);
      }
      Task.WaitAll(tasks.ToArray());
我非常感谢熟悉Task和HttpClient可能的怪癖的人提供的任何帮助

注意:Run是一个扩展方法,用于帮助处理异步异常

        public static Task Run(this Task task)
        {
            task.Start();
            task.ContinueWith(t => 
            {
                if(t.Exception != null)
                    Log.Error(t.Exception.Flatten().ToString());
            });
            return task;
        }

很难给出一个明确的答案,因为我们没有所有的细节,但这里有一个示例实现,说明如何触发HTTP请求。请注意,所有异步操作都处于等待状态-
Result
Wait
/
WaitAll
未使用。你几乎不应该需要/使用任何一种——它们是同步阻塞的,可能会产生问题

还请注意,没有为HTTP客户端定义全局cookie容器、默认标头等。如果您需要这些东西,只需创建单独的HttpRequestMessage对象,并添加您需要添加的任何头。不要使用全局属性-只设置每个请求的属性要干净得多

// Globally defined HTTP client.
private static readonly HttpClient _httpClient = new HttpClient();

// Other stuff here...

private async Task SomeFunctionToGetContent()
{
    var requestTasks = new List<Task<HttpResponseMessage>>();
    var responseTasks = new List<Task>();

    for (var i = 0; i < 5; i++)
    {
        // Fake URI but still based on the counter (or other
        // variable, similar to page in the question)
        var uri = new Uri($"https://.../{i}.html");

        requestTasks.Add(_httpClient.GetAsync(uri));
    }

    await (Task.WhenAll(requestTasks));

    for (var i = 0; i < 5; i++)
    {
        var response = await (requestTasks[i]);

        responseTasks.Add(HandleResponse(response));
    }

    await (Tasks.WhenAll(responseTasks));
}

private async Task HandleResponse(HttpResponseMessage response)
{
    try
    {
        if (response.Content != null)
        {
            var content = await (response.Content.ReadAsStringAsync());

            // do something with content here; check IsSuccessStatusCode to
            // see if the request failed or succeeded
        }
        else
        {
            // Do something when no content
        }
    }
    finally
    {
        response.Dispose();
    }
}
//全局定义的HTTP客户端。
私有静态只读HttpClient _HttpClient=新HttpClient();
//这里还有其他东西。。。
私有异步任务SomeFunctionToGetContent()
{
var requestTasks=新列表();
var responseTasks=新列表();
对于(变量i=0;i<5;i++)
{
//伪造URI,但仍然基于计数器(或其他
//变量,类似于问题中的页面)
var uri=新的uri($”https://.../{i} .html”);
Add(_httpClient.GetAsync(uri));
}
等待(Task.WhenAll(requestTasks));
对于(变量i=0;i<5;i++)
{
var response=wait(requestTasks[i]);
添加(HandleResponse(response));
}
等待(任务。等待(响应任务));
}
专用异步任务句柄响应(HttpResponseMessage响应)
{
尝试
{
if(response.Content!=null)
{
var content=await(response.content.ReadAsStringAsync());
//在此处对内容执行操作;选中IsSuccessStatusCode以
//查看请求是否失败或成功
}
其他的
{
//当没有内容时做某事
}
}
最后
{
response.Dispose();
}
}

我的第一个猜测是
页面
变量在
任务
实际开始之前发生了变化。如果
页面
变量是一个通过值(int、short等)传递的变量,那么它应该无关紧要,但是如果它是通过引用(字符串、对象、类)传递的,那么它可以创建您看到的竞争条件。我会尝试在
DoPageLoad
中捕获page的值,看看它是否是您所期望的“当我重用一个客户端实例时,我遇到了这个问题。但是自从我开始为每个调用实例化新客户端后,问题就消失了。”-那么,您是只对旧代码有这个问题,还是对新代码有这个问题(您为每个请求创建了一个新的
HttpClient
)。在重用
HttpClient
时,确实没有理由发生这种情况,除非您做了一些错误的事情(可能每次调用时都会更改?),否则“用于实例化一次并在应用程序的整个生命周期内重复使用。为每个请求实例化HttpClient类将耗尽重载下可用的套接字数。"不要创建多个
HttpClient
实例-您做错了什么,找到它并修复它。创建太多的客户端可能会耗尽系统资源。您也在同步调用它-您几乎不应该触摸
任务。结果
但在启动异步操作时使用
等待
。谢谢,我将尝试此操作出去,让你知道结果
// Globally defined HTTP client.
private static readonly HttpClient _httpClient = new HttpClient();

// Other stuff here...

private async Task SomeFunctionToGetContent()
{
    var requestTasks = new List<Task<HttpResponseMessage>>();
    var responseTasks = new List<Task>();

    for (var i = 0; i < 5; i++)
    {
        // Fake URI but still based on the counter (or other
        // variable, similar to page in the question)
        var uri = new Uri($"https://.../{i}.html");

        requestTasks.Add(_httpClient.GetAsync(uri));
    }

    await (Task.WhenAll(requestTasks));

    for (var i = 0; i < 5; i++)
    {
        var response = await (requestTasks[i]);

        responseTasks.Add(HandleResponse(response));
    }

    await (Tasks.WhenAll(responseTasks));
}

private async Task HandleResponse(HttpResponseMessage response)
{
    try
    {
        if (response.Content != null)
        {
            var content = await (response.Content.ReadAsStringAsync());

            // do something with content here; check IsSuccessStatusCode to
            // see if the request failed or succeeded
        }
        else
        {
            // Do something when no content
        }
    }
    finally
    {
        response.Dispose();
    }
}