简体   繁体   English

使用 System.Net.Http.HttpClient 的并行 HTTP 请求

[英]Parallel HTTP requests using System.Net.Http.HttpClient

I'm trying to figure out the correct way to parallelize HTTP requests using Task and async/await .我试图找出使用Taskasync/await并行化 HTTP 请求的正确方法。 I'm using the HttpClient class which already has async methods for retrieving data.我正在使用HttpClient class ,它已经具有用于检索数据的异步方法。 If I just call it in a foreach loop and await the response, only one request gets sent at a time (which makes sense because during the await , control is returning to our event loop, not to the next iteration of the foreach loop).如果我只是在 foreach 循环中调用它并等待响应,则一次只发送一个请求(这是有道理的,因为在await期间,控制将返回到我们的事件循环,而不是 foreach 循环的下一次迭代)。

My wrapper around HttpClient looks as such我的HttpClient包装器看起来像这样

public sealed class RestClient
{
    private readonly HttpClient client;

    public RestClient(string baseUrl)
    {
        var baseUri = new Uri(baseUrl);

        client = new HttpClient
        {
            BaseAddress = baseUri
        };
    }

    public async Task<Stream> GetResponseStreamAsync(string uri)
    {
        var resp = await GetResponseAsync(uri);
        return await resp.Content.ReadAsStreamAsync();
    }

    public async Task<HttpResponseMessage> GetResponseAsync(string uri)
    {
        var resp = await client.GetAsync(uri);
        if (!resp.IsSuccessStatusCode)
        {
            // ...
        }

        return resp;
    }

    public async Task<T> GetResponseObjectAsync<T>(string uri)
    {
        using (var responseStream = await GetResponseStreamAsync(uri))
        using (var sr = new StreamReader(responseStream))
        using (var jr = new JsonTextReader(sr))
        {
            var serializer = new JsonSerializer {NullValueHandling = NullValueHandling.Ignore};
            return serializer.Deserialize<T>(jr);
        }
    }

    public async Task<string> GetResponseString(string uri)
    {
        using (var resp = await GetResponseStreamAsync(uri))
        using (var sr = new StreamReader(resp))
        {
            return sr.ReadToEnd();
        }
    }
}

And the code invoked by our event loop is我们的事件循环调用的代码是

public async void DoWork(Action<bool> onComplete)
{
    try
    {
        var restClient = new RestClient("https://example.com");

        var ids = await restClient.GetResponseObjectAsync<IdListResponse>("/ids").Ids;

        Log.Info("Downloading {0:D} items", ids.Count);
        using (var fs = new FileStream(@"C:\test.json", FileMode.Create, FileAccess.Write, FileShare.Read))
        using (var sw = new StreamWriter(fs))
        {
            sw.Write("[");

            var first = true;
            var numCompleted = 0;
            foreach (var id in ids)
            {
                Log.Info("Downloading item {0:D}, completed {1:D}", id, numCompleted);
                numCompleted += 1;
                try
                {
                    var str = await restClient.GetResponseString($"/info/{id}");
                    if (!first)
                    {
                        sw.Write(",");
                    }

                    sw.Write(str);

                    first = false;
                }
                catch (HttpException e)
                {
                    if (e.StatusCode == HttpStatusCode.Forbidden)
                    {
                        Log.Warn(e.ResponseMessage);
                    }
                    else
                    {
                        throw;
                    }
                }
            }

            sw.Write("]");
        }

        onComplete(true);
    }
    catch (Exception e)
    {
        Log.Error(e);
        onComplete(false);
    }
}

I've tried a handful of different approaches involving Parallel.ForEach , Linq.AsParallel , and wrapping the entire contents of the loop in a Task .我尝试了几种不同的方法,包括Parallel.ForEachLinq.AsParallel ,并将循环的全部内容包装在Task中。

The basic idea is to keep of track of all the asynchronous tasks, and awaiting them at once. 基本思想是跟踪所有异步任务,并立即等待它们。 The simplest way to do this is to extract the body of your foreach to a separate asynchronous method, and do something like this: 最简单的方法是将foreach的主体提取为单独的异步方法,并执行以下操作:

var tasks = ids.Select(i => DoWorkAsync(i));
await Task.WhenAll(tasks);

This way, the individual tasks are issued separately (still in sequence, but without waiting for the I/O to complete), and you await them all at the same time. 这样,各个任务将单独发布(仍然按顺序发布,但不等待I / O完成),并且您可以同时等待所有任务。

Do note that you will also need to do some configuration - HTTP is throttled by default to only allow two simultaneous connections to the same server. 请注意,您还需要进行一些配置 - 默认情况下,HTTP会受到限制,只允许两个同时连接到同一服务器。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

相关问题 System.Net.Http.HttpClient缓存 - System.Net.Http.HttpClient cache System.Net.Http.HttpClient 缓存行为 - System.Net.Http.HttpClient caching behavior 自动重试System.Net.Http.HttpClient - Automatic retry for the System.Net.Http.HttpClient 使用 System.Net.Http.HttpClient 时,有没有办法参数化 HTTP 方法? - Is there a way to parameterize the HTTP method when using System.Net.Http.HttpClient? System.Net.Http.HttpClient 禁用缓存(.Net 标准项目) - System.Net.Http.HttpClient Disable Caching (.Net Standart Project) 提供带有System.Net.Http.HttpClient和MVC的AntiForgery令牌 - Provide AntiForgery Token with System.Net.Http.HttpClient and MVC 读取通过System.Net.Http.HttpClient(ASP.NET MVC)完成的不成功HTTP请求的响应 - Reading response of non-successful HTTP requests done via System.Net.Http.HttpClient (ASP.NET MVC) 无法解析“System.Net.Http.HttpClient”类型的服务 - Unable to resolve service for type 'System.Net.Http.HttpClient' 是否可以对System.Net.Http.HttpClient使用持久连接? - Is it possible to use persistent connections with System.Net.Http.HttpClient? System.Net.Http.HttpClient如何选择身份验证类型? - How does the System.Net.Http.HttpClient select authentication type?
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM