美文网首页
Avoid async lambda in Parallel

Avoid async lambda in Parallel

作者: unbuilt | 来源:发表于2019-12-28 22:24 被阅读0次

并行与异步是提升程序性能的常用手段。但是应避免在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

相关文章

网友评论

      本文标题:Avoid async lambda in Parallel

      本文链接:https://www.haomeiwen.com/subject/ydceoctx.html