并行與異步是提升程序性能的常用手段牡借。但是應(yīng)避免在Parallel.For或者Parallel.ForEach中使用異步Lambda函數(shù)疏旨。
以下通過一個(gè)示例說明在Parallel中使用異步Lambda函數(shù)的問題。
版本一,初始版本,使用同步方法
假定我們需要在程序中處理一組任務(wù){ "A", "B", "C", "D" }
魏蔗,處理一個(gè)任務(wù)的方法簡(jiǎn)化為DoTask
持偏。一個(gè)初始的實(shí)現(xiàn)是在DoTasks
中同步執(zhí)行這些任務(wù)驼卖。
static void Log(string message)
{
Console.WriteLine("{0} {1} {2}", DateTime.UtcNow.ToString("HH:mm:ss fff"), message);
}
static bool DoTask(string key)
{
Thread.Sleep(1000);
Log(key);
return true;
}
static void DoTasks(string[] keys)
{
foreach (var key in keys)
{
DoTask(key);
}
}
static async Task Main(string[] args)
{
Log("Start");
var keys = new string[] { "A", "B", "C", "D" };
DoTasks(keys);
Log("End");
}
程序運(yùn)行后,可以得到類似這樣的輸出:
13:24:58 685 Start
13:24:59 737 A
13:25:00 738 B
13:25:01 740 C
13:25:02 741 D
13:25:02 741 End
可見程序運(yùn)行花了4秒多一點(diǎn)兒鸿秆。
版本二酌畜,并行處理任務(wù)
通過使用Parallel.ForEach可以同時(shí)執(zhí)行多個(gè)任務(wù)。
static void DoTasksParallel(string[] keys)
{
Parallel.ForEach(keys, key =>
{
DoTask(key);
});
}
程序運(yùn)行后卿叽,得到輸出:
13:28:12 670 Start
13:28:13 856 C
13:28:13 856 B
13:28:13 863 A
13:28:13 864 D
13:28:13 864 End
可見并行提升了程序運(yùn)行速度桥胞,這次只花了1秒多點(diǎn)兒。
版本三考婴,并行中使用異步
假如處理任務(wù)的過程中有異步操作贩虾,我們很可能會(huì)實(shí)現(xiàn)一個(gè)新的DoTaskAsync
方法以提升程序性能。
static async Task<bool> DoTaskAsync(string key)
{
await Task.Delay(1000);
Log(key);
return true;
}
static void DoTasksParallelAsync(string[] keys)
{
Parallel.ForEach(keys, async key =>
{
await DoTaskAsync(key);
});
}
這次程序很快就運(yùn)行結(jié)束了沥阱,但是并沒有輸出執(zhí)行任務(wù)的日志缎罢。
13:39:13 890 Start
13:39:14 072 End
可見,當(dāng)程序結(jié)束時(shí)考杉,任務(wù)還沒有完成策精。如果我們?cè)?code>Main函數(shù)最后加入Console.ReadLine();
等待輸入,再次運(yùn)行程序會(huì)得到這樣的輸出:
13:43:30 133 Start
13:43:30 312 End
13:43:31 321 D
13:43:31 321 C
13:43:31 321 A
13:43:31 321 B
這說明Parallel.ForEach沒有等待 async lambda 運(yùn)行結(jié)果就執(zhí)行后續(xù)操作了奔则。通常蛮寂,這不是我們想要的程序行為。
問題在于 async lambda 會(huì)被轉(zhuǎn)換成 async void 函數(shù)易茬,而 async void 函數(shù)應(yīng)慎用。因?yàn)樗姆祷刂凳莢oid而不是Task及老,沒有簡(jiǎn)單的方法可以讓調(diào)用者知道它是否結(jié)束了抽莱。
避免在Parallel中使用 async lambda 函數(shù)。
版本四骄恶,異步任務(wù)
其實(shí)對(duì)于執(zhí)行異步操作的任務(wù)來說食铐,可以直接獲取到對(duì)應(yīng)的Task,然后等待任務(wù)運(yùn)行結(jié)束僧鲁,不一定需要使用Parallel虐呻。
static async Task DoTasksAsync(string[] keys)
{
var tasks = keys.Select(x => DoTaskAsync(x));
await Task.WhenAll(tasks);
}
程序運(yùn)行后,得到輸出:
14:05:27 255 Start
14:05:28 418 B
14:05:28 418 A
14:05:28 418 D
14:05:28 418 C
14:05:28 422 End
可以看到程序運(yùn)行時(shí)間也是1秒多點(diǎn)兒寞秃。
如果任務(wù)中有CPU密集計(jì)算斟叼,也可以使用Task.Run等方法結(jié)合Parallel實(shí)現(xiàn)并行。