[英]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代碼..大聲思考。
不,將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處理線程”
一個接近的選擇可能是這樣的:
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.