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

What is the proper way to send several async API requests and process the responses in parallel?

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

方法一

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
        });

方法二

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);

备注:

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

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

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

// 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()。如果你有一个异步进程,这些都不会有任何好处。


回复您的评论:

I assume the code would go inside the parallel foreach right?

不,您不会在此处使用 Parallel.ForEach,因为您仍在调用异步进程。 Parallel.ForEach 用于将 work 加载到多个线程,但这不是您想要在此处执行的操作。您有异步进程可以同时 运行 而不需要额外的线程。

所以如果你有很多这样的东西你想异步调用,只需调用它们并收集 Tasks 稍后等待它们。

But what if the requests had to be sequential such as paged responses where the first request would return a URL for the next page request and so on?

然后您需要使您使用 await 的调用相互依赖,以确保在继续之前完成异步过程。例如,如果 propertyTwo 调用依赖于 propertyOne,那么您仍然可以先进行 all 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();
}

Also, if the calling method is not async, would Task.WhenAll(oneTask, twoTask, threeTask).GetAwaiter().GetResult() be appropriate in this case?

没有。从同步方法调用异步方法通常不是您应该做的事情。如果调用异步方法,则应使调用方法也异步。有一种方法可以使这项工作有效,但您将不得不处理潜在的死锁,并且通常应该知道自己在做什么。另见 this question.