【问题标题】:What is a preferred/efficient way to send hundreds or thousands of http requests (c#)?发送数百或数千个 http 请求 (c#) 的首选/有效方式是什么?
【发布时间】:2021-03-21 14:52:31
【问题描述】:

在我的场景中,我需要处理一个项目列表(伪代码很糟糕),其数量可能是数百或数千。那么,什么是处理这个问题的有效方法呢?这种场景有一些模式/最佳实践吗?

一些具体问题是:

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

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

    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
    }

【问题讨论】:

  • “Micrsoft doc 不建议在紧密循环中使用 async/await”- 这个文档在哪里?
  • @PauloMorgado,请参阅:docs.microsoft.com/en-us/dotnet/csharp/async,并搜索“在可能的情况下考虑使用 ValueTask”部分。
  • @zjg.robin,这并不是说“不建议在紧密循环中使用 async/await”。这是对性能关键的紧密循环分配的一般评论。并且建议不要使用异步,而是使用ValueTask而不是Task

标签: c# concurrency async-await task


【解决方案1】:

由于这些是 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

您可以通过多种方式配置管道,并且它们有很多选项,这只是一个示例

【讨论】:

  • 对于让默认任务调度程序所有这些任务的简单方法,我担心如果项目数量很大,它是否会消耗所有线程资源。我们的服务运行在一台对资源非常敏感的机器上,同时还有许多其他服务在运行。
猜你喜欢
  • 2018-07-12
  • 1970-01-01
  • 2012-10-13
  • 2023-01-12
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-02-18
  • 2013-12-05
相关资源
最近更新 更多