异步方法不能并行运行

时间:2015-08-31 19:49:07

标签: c# asynchronous task-parallel-library

在下面的代码中,在B方法中,代码为Trace.TraceInformation(" B-Started");永远不会被召唤。

该方法应该并行运行吗?

using System.Collections.Generic;
using System.Diagnostics;
using System.Threading.Tasks;

namespace ConsoleApplication1
{
    class Program
    {
        private static async Task A()
        {
            for (;;)
            {
            }
        }


        private static async Task B()
        {
            Trace.TraceInformation("B - Started");
        }

        static void Main(string[] args)
        {
            var tasks = new List<Task> { A(), B() };
            Task.WaitAll(tasks.ToArray());
        }
    }
}

3 个答案:

答案 0 :(得分:9)

简短回答

不,当你编写两个async方法时,它们确实没有并行运行。将await Task.Yield();添加到第一个方法(例如在循环内)允许它们这样做,但是有更合理和简单的方法,高度取决于您实际需要的内容(交错执行)一个线程?在多个线程上实际并行执行?)。

答案很长

首先,将函数声明为async本身并不会使它们以异步方式运行。它相当简化了语法 - 请在此处阅读有关概念的更多信息:Asynchronous Programming with Async and Await

有效A根本不是异步的,因为其方法体内没有一个await。第一次使用await时的说明会像常规方法一样同步运行。

从那时起,您await确定接下来发生的事情的对象,即剩余方法运行的上下文。

要在另一个线程上执行强制任务,请使用Task.Run或类似内容。

在这种情况下,添加await Task.Yield()可以解决问题,因为当前的同步上下文是null,这确实会导致任务调度程序(应该是ThreadPoolTaskScheduler)执行剩余的指令在线程池线程上 - 某些环境或配置可能会导致您只有其中一个,因此事情仍然不能并行运行。

<强>摘要

故事的寓意是:要注意两个概念之间的差异:

  • 并发(合理使用async / await启用)和
  • 并行性(仅当并发任务计划正确时,如果您使用Task.RunThread等强制执行此操作,则会发生这种情况。在这种情况下,使用async完全无关紧要)

答案 1 :(得分:2)

async修饰符不是魔法生成线程 - 此处标记。它的唯一目的是让编译器知道一个方法可能依赖于某些异步操作(一个复杂的数据处理线程,I / O ......)所以它必须设置一个状态机来协调这些异步操作产生的回调。

要使A在另一个线程上运行,你可以使用Task.Run调用它,它将调用包装在一个带有Task对象的新线程上,你可以等待它。请注意await - 一个方法并不意味着你的代码与A的执行并行运行:它将直到你await任务对象的行,告诉你编译器需要 Task对象返回的值。在这种情况下,await - ing Task.Run(A)将有效地让您的程序永远运行,等待A返回,这是永远不会发生的事情(除非计算机出现故障)。

请记住,将方法标记为异步但实际上没有等待任何内容只会产生编译器警告的效果。如果你等待的东西不是真正的异步(它会在调用线程上立即返回类似Task.FromResult的东西),这将意味着你的程序需要运行时速度惩罚。然而,它非常轻微。

答案 2 :(得分:1)

不,所示的方法不应该&#34;并行运行&#34;。

为什么永远不会调用B - 您有基本上通过一系列tasks次调用构建的任务列表.Add - 首先是{{1>} 的结果 }} 被添加。由于A()方法没有任何A,因此它将在同一个线程上同步运行。然后会调用await

现在B()永远不会完成(它处于无限循环中),所以真正的代码甚至无法调用A

请注意,即使创建成功,代码也永远不会完成B,因为WaitAll仍处于无限循环中。

如果你想要方法&#34;并行运行&#34;您需要在新线程上隐式/显式地运行它们(即使用ATask.Run),或者对于I / O绑定调用,让方法使用Thread.Start释放线程。