在一些.NET Parallel.ForEach()代码中做一些异步/等待是否可以?

时间:2014-05-19 04:48:16

标签: c# .net parallel-processing async-await parallel.foreach

鉴于以下代码,{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代码..大声思考。

4 个答案:

答案 0 :(得分:6)

不,将asyncParalell.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处理线程”

Stephen toub addressed this here

答案 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