簡體   English   中英

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

[英]Is it OK to do some async/await inside some .NET Parallel.ForEach() code?

考慮下面的代碼,它是async/await一個內部Parallel.ForEach

例如。

Parallel.ForEach(names, name =>
{
    // Do some stuff...

    var foo = await GetStuffFrom3rdPartyAsync(name);

    // Do some more stuff, with the foo.
});

還是有一些我需要知道的問題?

編輯:不知道這是否編譯,順便說一句。 只是Pseduo代碼..大聲思考。

不,將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在這里解決了這個問題

一個接近的選擇可能是這樣的:

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 ,“一直向下”:

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

從名稱來看,我假設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.
}));

正如@Sriram Sakthivel所指出的,使用Parallel.ForEach和異步lambda會有一些問題。 Steven Toub的ForEachASync可以做到相同。 在這里談到它,但這里是代碼:

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工作之前將打印完成行。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM