发送成百上千个 http 请求的 preferred/efficient 方法是什么 (c#)?

What is a preferred/efficient way to send hundreds or thousands of http requests (c#)?

在我的场景中,我需要处理一个项目列表(伪代码是吹的),其数量可能是数百或数千。那么,处理这个问题的有效方法是什么?有没有针对这种场景的一些patterns/best实践?

一些具体问题是:

  1. 我想我应该首先将 QueryResultAsync 上的同步调用更改为异步,但 Micrsoft 文档不建议在紧密循环中使用 async/await。那么,任何解决方法?
  2. 我是否应该考虑同时使用多个任务 运行 以减少延迟?例如,假设有 100 个项目要处理,我同时创建 10 个任务(每个项目一个)运行 和其中的 WaitAll(),然后将有 10 个回合来完成 100 个项目。这样更好吗?
  3. 我是否应该考虑 producer/consumer 模式,其中 3 个生产者负责 Web 请求,一个消费者处理结果?

如果需要您的(场景)信息,请告诉我。

    public List<string> Process(List<string> items) 
    {
       List<string> resultItems = new List<string>(items.Count);
       foreach (string item in items)
       {
          string result = QueryResultAsync(item).GetAwaiter().GetResult(); // need to send http request for each item with different urls
          resultItems.Add(ProcessResult(result);
       }
       
       return resultItems;
    }

    private static string ProcessResult(string item){
        // some plain processing logic without I/O
        return result; // a string value
    }

由于这些是 IO 绑定工作负载,您可以简单地使用 async 和 await 模式,以及 Task.WhenAll 让任务调度程序处理细节

public async Task<List<string>> Process(List<string> items)
{
   var tasks = items.Select(x => QueryResultAsync(x));
   var results = await Task.WhenAll(tasks);
   return results.Select(x => ProcessResult(x)).ToList();
}

如果您对多个生产者感兴趣,您可以使用 Tpl 数据流管道,它可以更好地分区和处理最大并发请求,然后将结果通过管道传输到处理器。

一个荒谬的例子

// create some blocks
var queryBlock = new TransformBlock<string, string>(
   QueryResultAsync,
   new ExecutionDataflowBlockOptions()
   {
      EnsureOrdered = false,
      MaxDegreeOfParallelism = 50 // ??
   });

var processBlock = new TransformBlock<string, string>(
   ProcessResult,
   new ExecutionDataflowBlockOptions()
   {
      MaxDegreeOfParallelism = 5, // ??
   });

var someOtherAction = new ActionBlock<string>(x => Console.WriteLine(x));

//link them together
queryBlock.LinkTo(processBlock, new DataflowLinkOptions() {PropagateCompletion = true});
processBlock.LinkTo(someOtherAction, new DataflowLinkOptions() {PropagateCompletion = true});

// produce some junk
for (var i = 0; i < 10; i++)
   await queryBlock.SendAsync(i.ToString());

// wait for it all to finish
queryBlock.Complete();
await someOtherAction.Completion;

输出

0
8
7
1
2
5
6
3
4
9

配置管道的方法有很多种,它们有很多选项,这只是一个例子