C# 发送多个异步API请求并并行处理响应的正确方法是什么?

C# 发送多个异步API请求并并行处理响应的正确方法是什么?,c#,asp.net-core,task,parallel.foreach,C#,Asp.net Core,Task,Parallel.foreach,我有一个项目列表,对于每个项目,我需要执行几个异步API请求并处理响应,我看到了几个实现,它们在执行时间方面都很相似,但我想知道它们之间的区别 方法1 Parallel.ForEach(items, new ParallelOptions { MaxDegreeOfParallelism = 10 }, item => { var response = item.propertyOne.GetAsync().GetAwaiter().GetResul

我有一个项目列表,对于每个项目,我需要执行几个异步API请求并处理响应,我看到了几个实现,它们在执行时间方面都很相似,但我想知道它们之间的区别

方法1

Parallel.ForEach(items, new ParallelOptions { MaxDegreeOfParallelism = 10 }, item =>
        {
            var response = item.propertyOne.GetAsync().GetAwaiter().GetResult();
            //process it
            var response = item.propertyTwo.GetAsync().GetAwaiter().GetResult();
            //process it
            var response = item.propertyThree.GetAsync().GetAwaiter().GetResult();
            //process it
        });
方法2

Parallel.ForEach(items, new ParallelOptions { MaxDegreeOfParallelism = 10 }, item =>
        {
            Task.Run(async () =>
            {
                var response = await item.propertyOne.GetAsync();
            }).GetAwaiter().GetResult();
            //process it
            Task.Run(async () =>
            {
                var response = await item.propertyTwo.GetAsync();
            }).GetAwaiter().GetResult();
            //process it
            Task.Run(async () =>
            {
                var response = await item.propertyThreee.GetAsync();
            }).GetAwaiter().GetResult();
            //process it
        });
方法3假设应用了某种边界机制,以避免任务充斥系统

List<Task> tasksToAwait = new List<Task>();
        foreach (var item in items)
        {
            tasksToAwait.Add(Task.Run(async () =>
            {
                var response = await item.propertyOne.GetAsync();
                //process it
                var response = await item.propertyTwo.GetAsync();
                //process it
                var response = await item.propertyThree.GetAsync();
                //process it
            }));
        }
        await Task.WhenAll(taskToAwait);
List tasksToAwait=new List();
foreach(项目中的var项目)
{
taskstowait.Add(Task.Run)(异步()=>
{
var response=wait item.propertyOne.GetAsync();
//处理它
var response=wait item.propertyTwo.GetAsync();
//处理它
var response=wait item.propertytree.GetAsync();
//处理它
}));
}
等待任务。WhenAll(任务等待);
注:

  • 我使用GetAwaiter().GetResult()而不是Wait(),因为它不接受异常
  • 为了处理响应,必须以某种方式等待请求
  • 这是作为后台任务执行的,所以我不介意阻塞调用线程
  • 第三种方法略快于其他两种方法,第二种方法略快于第一种方法
  • 我无法控制通过GetAsync()调用的异步API

推荐哪一种?如果没有,你有什么建议?另外,它们有什么不同?为什么会有执行时间差异?

使用Parallel.Foreach并将所有任务添加到列表中,使用WhenAll方法等待所有任务完成。

由于您的方法是异步的,只需调用它们,而不是单独等待它们,等待所有人,以确保所有三个异步进程都已完成。然后,您可以再次使用
wait
打开结果:

// start all asynchronous processes at once for all three properties
var oneTask = item.propertyOne.GetAsync();
var twoTask = item.propertyTwo.GetAsync();
var threeTask = item.propertyThree.GetAsync();

// await the completion of all of those tasks
await Task.WhenAll(oneTask, twoTask, threeTask);

// access the individual results; since the tasks are already completed, this will
// unwrap the results from the tasks instantly:
var oneResult = await oneTask;
var twoResult = await twoTask;
var threeResult = await threeTask;
通常,在处理异步内容时,避免调用
.GetResult()
.Result
.Wait()
。如果您有一个异步进程,这两种方法都不会有任何好处


要答复您的评论:

我想代码应该在并行foreach中,对吗

不,您不会在这里使用
Parallel.ForEach
,因为您仍在调用异步进程
Parallel.ForEach
用于将工作加载到多个线程,但这不是您想要在这里执行的操作。您有异步进程,它们都可以并发运行,而不需要额外的线程

因此,如果有很多东西需要异步调用,只需调用所有这些东西并收集
任务
等待它们

但是,如果请求必须是连续的,例如分页响应,其中第一个请求将返回下一个页面请求的URL等等,该怎么办

然后,您需要使调用相互依赖,并使用
wait
确保异步过程完成后才能继续。例如,如果两个
propertyTo
调用依赖于
propertyOne
,那么您仍然可以首先进行所有
propertyOne
调用,并且只有在这些调用完成后才能继续

如果将逻辑拆分为单独的方法,您会注意到这会变得更容易:

var itemTasks = items.Select(item => GetPropertiesOneTwoThreeAsync(item));
await Task.WhenAll(itemTasks);

private Task GetPropertiesOneTwoThreeAsync(Item item)
{
    // get the properties sequentially
    var oneResult = await item.propertyOne.GetAsync();
    var twoResult = await item.propertyTwo.GetAsync();
    var threeResult = await item.propertyThree.GetAsync();
}
另外,如果调用方法不是异步的,那么
Task.WhenAll(oneTask,twoTask,threeTask).GetAwaiter().GetResult()在这种情况下是否合适


不。从同步方法调用异步方法通常不是您应该做的事情。如果调用异步方法,则应使调用方法也异步。有一种方法可以让这一切顺利进行,但你必须处理好潜在的僵局,并且通常应该知道你在做什么。另请参见。

好问题,我通常只将任务存储在一个列表中,然后执行。好主意,我假设代码将进入并行foreach中,对吗?但是,如果请求必须是连续的,例如分页响应,其中第一个请求将返回下一个页面请求的URL等等,该怎么办?另外,如果调用方法不是异步的,Task.WhenAll(oneTask,twoTask,threeTask).GetAwaiter().GetResult()在这种情况下是否合适?Thanks@DaniMazahreh我编辑了我的答案以回答您的后续问题。感谢您的后续提问,最后一段代码类似于我将坚持使用的第三种方法。除了加快创建任务,这还能完成什么?