鉴于以下代码,{em> OK 在async/await
内进行Parallel.ForEach
吗?
例如
Parallel.ForEach(names, name =>
{
// Do some stuff...
var foo = await GetStuffFrom3rdPartyAsync(name);
// Do some more stuff, with the foo.
});
或者是否有一些我需要了解的问题?
编辑:不知道这是否编译,顺便说一句。只是Pseduo代码..大声思考。答案 0 :(得分:6)
不,将async
与Paralell.Foreach
结合使用是没有意义的。
考虑以下示例:
private void DoSomething()
{
var names = Enumerable.Range(0,10).Select(x=> "Somename" + x);
Parallel.ForEach(names, async(name) =>
{
await Task.Delay(1000);
Console.WriteLine("Name {0} completed",name);
});
Console.WriteLine("Parallel ForEach completed");
}
您期望得到什么输出?
Name Somename3 completed
Name Somename8 completed
Name Somename4 completed
...
Parallel ForEach completed
这不会发生什么。它将输出:
Parallel ForEach completed
Name Somename3 completed
Name Somename8 completed
Name Somename4 completed
...
为什么呢?因为当ForEach
首先点击await
时方法实际返回,Parallel.ForEach
不知道它是异步的并且它已经完成了! await
之后的代码作为另一个线程上的延续运行不“Paralell处理线程”
答案 1 :(得分:4)
一个接近的选择可能是:
static void ForEach<T>(IEnumerable<T> data, Func<T, Task> func)
{
var tasks = data.Select(item =>
Task.Run(() => func(item)));
Task.WaitAll(tasks.ToArray());
}
// ...
ForEach(names, name => GetStuffFrom3rdPartyAsync(name));
理想情况下,您不应该使用Task.WaitAll
之类的阻止调用,如果您可以使整个方法链调用async
,&#34;一直向下&#34;在当前的调用堆栈中:
var tasks = data.Select(item =>
Task.Run(() => func(item)));
await Task.WhenAll(tasks.ToArray());
此外,如果您不在GetStuffFrom3rdPartyAsync
内执行任何受CPU约束的工作,Task.Run
可能会多余:
var tasks = data.Select(item => func(item));
答案 2 :(得分:4)
从名称来看,我假设GetStuffFrom3rdPartyAsync
是I / O绑定的。 Parallel
类专门用于CPU绑定代码。
在异步世界中,您可以启动多个任务,然后(异步)等待所有任务使用Task.WhenAll
完成。由于您从序列开始,因此将每个元素投影到异步操作可能最容易,然后等待所有这些操作:
await Task.WhenAll(names.Select(async name =>
{
// Do some stuff...
var foo = await GetStuffFrom3rdPartyAsync(name);
// Do some more stuff, with the foo.
}));
答案 3 :(得分:2)
正如@Sriram Sakthivel所指出的,使用Parallel.ForEach
和异步lambda会有一些问题。 Steven Toub的ForEachASync
可以做同等的事情。他谈到了here,但这里是代码:
public static class Extensions
{
public static Task ForEachAsync<T>(this IEnumerable<T> source, int dop, Func<T, Task> body)
{
return Task.WhenAll(
from partition in Partitioner.Create(source).GetPartitions(dop)
select Task.Run(async delegate {
using (partition) while (partition.MoveNext()) await body(partition.Current);
}));
}
}
它使用Partitioner
类创建负载平衡分区程序(doco),并允许您使用dop
参数指定要运行的线程数。看看它和Parallel.ForEach
之间的区别。请尝试以下代码。
class Program
{
public static async Task GetStuffParallelForEach()
{
var data = Enumerable.Range(1, 10);
Parallel.ForEach(data, async i =>
{
await Task.Delay(1000 * i);
Console.WriteLine(i);
});
}
public static async Task GetStuffForEachAsync()
{
var data = Enumerable.Range(1, 10);
await data.ForEachAsync(5, async i =>
{
await Task.Delay(1000 * i);
Console.WriteLine(i);
});
}
static void Main(string[] args)
{
//GetStuffParallelForEach().Wait(); // Finished printed before work is complete
GetStuffForEachAsync().Wait(); // Finished printed after all work is done
Console.WriteLine("Finished");
Console.ReadLine();
}
如果您运行GetStuffForEachAsync
,程序将等待所有工作完成。如果您运行GetStuffParallelForEach
,则在工作完成之前将打印行Finished
。