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

Dan*_*reh 2 c# task parallel.foreach asp.net-core

我有一个项目列表,对于每个项目,我需要执行几个异步 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
        });
Run Code Online (Sandbox Code Playgroud)

方法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
        });
Run Code Online (Sandbox Code Playgroud)

方法 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);
Run Code Online (Sandbox Code Playgroud)

笔记:

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

推荐其中哪一项?如果没有,您有何建议?另外,它们有何不同以及为什么存在执行时间差异?

pok*_*oke 5

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

\n\n
// start all asynchronous processes at once for all three properties\nvar oneTask = item.propertyOne.GetAsync();\nvar twoTask = item.propertyTwo.GetAsync();\nvar threeTask = item.propertyThree.GetAsync();\n\n// await the completion of all of those tasks\nawait Task.WhenAll(oneTask, twoTask, threeTask);\n\n// access the individual results; since the tasks are already completed, this will\n// unwrap the results from the tasks instantly:\nvar oneResult = await oneTask;\nvar twoResult = await twoTask;\nvar threeResult = await threeTask;\n
Run Code Online (Sandbox Code Playgroud)\n\n

一般来说,在处理异步内容时,应不惜一切代价避免调用.GetResult().Result.Wait()。如果你有一个异步进程,这些都不会产生任何好处。

\n\n
\n\n

回复您的评论:

\n\n
\n

我假设代码会进入并行 foreach 内部,对吧?

\n
\n\n

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

\n\n

因此,如果您有很多想要异步调用的东西,只需调用它们并收集 sTask以便稍后等待它们。

\n\n
\n

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

\n
\n\n

然后,您需要使调用相互依赖,await以确保异步过程在继续之前完成。例如,如果propertyTwo调用依赖于propertyOne,那么您仍然可以先进行所有 propertyOne调用,并且仅在这些调用完成后才继续。

\n\n

如果您将逻辑分成单独的方法,您会发现这变得更容易:

\n\n
var itemTasks = items.Select(item => GetPropertiesOneTwoThreeAsync(item));\nawait Task.WhenAll(itemTasks);\n\nprivate Task GetPropertiesOneTwoThreeAsync(Item item)\n{\n    // get the properties sequentially\n    var oneResult = await item.propertyOne.GetAsync();\n    var twoResult = await item.propertyTwo.GetAsync();\n    var threeResult = await item.propertyThree.GetAsync();\n}\n
Run Code Online (Sandbox Code Playgroud)\n\n
\n

另外,如果调用方法不是异步的,Task.WhenAll(oneTask, twoTask, threeTask).GetAwaiter().GetResult()在这种情况下是否合适?

\n
\n\n

不可以。从同步方法调用异步方法通常不是您应该做的事情。如果调用异步方法,则应该使调用方法也异步。有多种方法可以实现这一点,但您将不得不处理潜在的僵局,并且通常应该知道您在做什么。另请参阅这个问题

\n