Avoid async lambda in Parallel

并行与异步是提升程序性能的常用手段。但是应避免在Parallel.For或者Parallel.ForEach中使用异步Lambda函数。

以下通过一个示例说明在Parallel中使用异步Lambda函数的问题。

版本一,初始版本,使用同步方法

假定我们需要在程序中处理一组任务{ "A", "B", "C", "D" },处理一个任务的方法简化为DoTask。一个初始的实现是在DoTasks中同步执行这些任务。

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

程序运行后,可以得到类似这样的输出:

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

可见程序运行花了4秒多一点儿。

版本二,并行处理任务

通过使用Parallel.ForEach可以同时执行多个任务。

        static void DoTasksParallel(string[] keys)
        {
            Parallel.ForEach(keys, key =>
            {
                DoTask(key);
            });
        }

程序运行后,得到输出:

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

可见并行提升了程序运行速度,这次只花了1秒多点儿。

版本三,并行中使用异步

假如处理任务的过程中有异步操作,我们很可能会实现一个新的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);
            });
        }

这次程序很快就运行结束了,但是并没有输出执行任务的日志。

13:39:13 890 Start
13:39:14 072 End

可见,当程序结束时,任务还没有完成。如果我们在Main函数最后加入Console.ReadLine();等待输入,再次运行程序会得到这样的输出:

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 运行结果就执行后续操作了。通常,这不是我们想要的程序行为。

问题在于 async lambda 会被转换成 async void 函数,而 async void 函数应慎用。因为它的返回值是void而不是Task,没有简单的方法可以让调用者知道它是否结束了。

避免在Parallel中使用 async lambda 函数。

版本四,异步任务

其实对于执行异步操作的任务来说,可以直接获取到对应的Task,然后等待任务运行结束,不一定需要使用Parallel。

        static async Task DoTasksAsync(string[] keys)
        {
            var tasks = keys.Select(x => DoTaskAsync(x));
            await Task.WhenAll(tasks);
        }

程序运行后,得到输出:

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

可以看到程序运行时间也是1秒多点儿。

如果任务中有CPU密集计算,也可以使用Task.Run等方法结合Parallel实现并行。

参考资料

  1. Asynchronous programming
  2. Async/Await - Best Practices in Asynchronous Programming
  3. Concurrency in C# Cookbook
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 217,734评论 6 505
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,931评论 3 394
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,133评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,532评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,585评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,462评论 1 302
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,262评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,153评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,587评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,792评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,919评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,635评论 5 345
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,237评论 3 329
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,855评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,983评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,048评论 3 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,864评论 2 354

推荐阅读更多精彩内容

  • 本文用来介绍 iOS 多线程中 GCD 的相关知识以及使用方法。这大概是史上最详细、清晰的关于 GCD 的详细讲...
    花花世界的孤独行者阅读 500评论 0 1
  • 本篇博客共分以下几个模块来介绍GCD的相关内容: 多线程相关概念 多线程编程技术的优缺点比较? GCD中的三种队列...
    有梦想的老伯伯阅读 1,020评论 0 4
  • 室友匆忙的抱住我,一只手偷偷塞给我一张纸条后便撒手人寰。她就那样伏在我身上,再也叫不醒了。 纸条上写着“对不起,我...
    豆子_f阅读 515评论 3 7
  • 我在工作差不多半年的时候,发现自己的一个优点,遇到自己难受的事情,会很快的转换心情,或者说是安慰自己。在工作一年之...
    池塘里的雨阅读 155评论 0 0
  • 2017年4月14日 星期五 多云 现在我们的花样跳绳阳光大课间终于有点模样了,孩子们开始有了跳绳的感觉,练得...
    红叶与蓝鲸的美好相遇阅读 193评论 0 0