发送成百上千个 http 请求的 preferred/efficient 方法是什么 (c#)?
What is a preferred/efficient way to send hundreds or thousands of http requests (c#)?
在我的场景中,我需要处理一个项目列表(伪代码是吹的),其数量可能是数百或数千。那么,处理这个问题的有效方法是什么?有没有针对这种场景的一些patterns/best实践?
一些具体问题是:
- 我想我应该首先将 QueryResultAsync 上的同步调用更改为异步,但 Micrsoft 文档不建议在紧密循环中使用 async/await。那么,任何解决方法?
- 我是否应该考虑同时使用多个任务 运行 以减少延迟?例如,假设有 100 个项目要处理,我同时创建 10 个任务(每个项目一个)运行 和其中的 WaitAll(),然后将有 10 个回合来完成 100 个项目。这样更好吗?
- 我是否应该考虑 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
配置管道的方法有很多种,它们有很多选项,这只是一个例子
在我的场景中,我需要处理一个项目列表(伪代码是吹的),其数量可能是数百或数千。那么,处理这个问题的有效方法是什么?有没有针对这种场景的一些patterns/best实践?
一些具体问题是:
- 我想我应该首先将 QueryResultAsync 上的同步调用更改为异步,但 Micrsoft 文档不建议在紧密循环中使用 async/await。那么,任何解决方法?
- 我是否应该考虑同时使用多个任务 运行 以减少延迟?例如,假设有 100 个项目要处理,我同时创建 10 个任务(每个项目一个)运行 和其中的 WaitAll(),然后将有 10 个回合来完成 100 个项目。这样更好吗?
- 我是否应该考虑 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
配置管道的方法有很多种,它们有很多选项,这只是一个例子