发送多个异步 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);
备注:
- 我正在使用 GetAwaiter().GetResult() 而不是 Wait(),因为它不会吞噬异常。
- 必须以某种方式等待请求才能处理响应。
- 这是作为后台任务执行的,所以我不介意阻塞调用线程。
- 第三种方法比其他两种方法稍快,第二种方法比第一种方法稍快。
- 我无法控制通过 GetAsync() 调用的异步 API。
推荐其中哪一个?如果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 加载到多个线程,但这不是您想要在此处执行的操作。您有异步进程可以同时 运行 而不需要额外的线程。
所以如果你有很多这样的东西你想异步调用,只需调用它们并收集 Task
s 稍后等待它们。
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.
我有一个项目列表,对于每个项目,我需要执行几个异步 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);
备注:
- 我正在使用 GetAwaiter().GetResult() 而不是 Wait(),因为它不会吞噬异常。
- 必须以某种方式等待请求才能处理响应。
- 这是作为后台任务执行的,所以我不介意阻塞调用线程。
- 第三种方法比其他两种方法稍快,第二种方法比第一种方法稍快。
- 我无法控制通过 GetAsync() 调用的异步 API。
推荐其中哪一个?如果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 加载到多个线程,但这不是您想要在此处执行的操作。您有异步进程可以同时 运行 而不需要额外的线程。
所以如果你有很多这样的东西你想异步调用,只需调用它们并收集 Task
s 稍后等待它们。
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.