异步方法不能并行运行
在下面的代码中,在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 { A(), B() }; Task.WaitAll(tasks.ToArray()); } } }
简短的回答
不,当你编写两个async
方法时,它们确实没有并行运行。 添加await Task.Yield();
你的第一个方法(例如在循环内) 会允许它们这样做,但是有更合理和直接的方法,高度取决于你真正需要的东西(在单个线程上交错执行?在多个线程上实际并行执行?)。
答案很长
首先,将函数声明为async
并不会使它们以异步方式运行。 它相当简化了语法 – 阅读更多关于这里的概念: 使用Async和Await进行异步编程
有效地A
根本不是异步的,因为它的方法体内没有单一的await
。 直到第一次使用await
指令同步运行就像常规方法一样。
从那时起,您await
的对象将确定接下来发生的事情,即剩余方法运行的上下文。
要强制在另一个线程上执行任务,请使用Task.Run
或类似操作。
在这种情况下,添加await Task.Yield()
可以解决这个问题,因为当前的同步上下文为null
,这确实会导致任务调度程序(应该是ThreadPoolTaskScheduler
)执行线程池线程上的其余操作 – 某些环境或配置可能会导致您只有一个,所以事情仍然不会并行运行。
摘要
故事的寓意是:要意识到两个概念之间的差异:
- 并发(通过合理使用
async
/await
启用)和 - 并行性(只有当并发任务以正确的方式进行调度时才会发生, 或者如果使用
Task.Run
,Thread
等强制执行它,在这种情况下,使用async
完全无关紧要)
async
修饰符不是魔法spawn-a-thread-here标记。 它的唯一目的是让编译器知道一个方法可能依赖于某些异步操作(一个复杂的数据处理线程,I / O ……),因此它必须设置一个状态机来协调这些异步操作产生的回调。
要使A
在另一个线程上运行,您可以使用Task.Run调用它,它将调用包装在一个带有Task对象的新线程上,您可以等待它。 请注意, await
一个方法并不意味着您的代码本身与A
的执行并行运行:它将一直await
Task对象,告诉编译器您需要 Task对象返回的值。 在这种情况下, await
-ing Task.Run(A)
将有效地使您的程序永远运行,等待A
返回,这是永远不会发生的事情(除非计算机出现故障)。
请记住,将方法标记为异步但实际上没有等待任何事情只会产生编译器警告的效果。 如果你等待的东西不是真正的异步(它会在调用线程上立即返回类似Task.FromResult的东西),这将意味着你的程序需要运行时速度惩罚。 然而,它非常轻微。
不,所示方法不会“并行”。
为什么B
从未被调用 – 你有通过一系列.Add
调用构建的任务列表 – 首先是添加A()
结果 。 由于A
方法没有任何await
,它将在同一个线程上同步运行。 之后会调用B()
。
现在A
永远不会完成(它坐在无限循环中)所以真正的代码甚至不能达到对B
调用。
请注意,即使创建成功,代码也永远不会完成WaitAll
因为A
仍处于无限循环中。
如果您希望方法“并行运行”,则需要在新线程上隐式/显式地运行它们(即使用Task.Run
或Thread.Start
),或者对于I / O绑定调用,让方法使用await
释放线程。