异步方法不是 运行 并行
Async method not running in parallel
在下面的代码中,在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());
}
}
}
不,显示的方法预计不会 "run in parallel"。
为什么从未调用 B
- 你有任务列表 tasks
通过本质上一系列 .Add
调用构建 - 第一个是 result[=35=添加了 A()
的 ]。由于 A
方法没有任何 await
它将 运行 在同一个线程上同步完成。然后 B()
会被调用。
现在 A
永远不会完成(它处于无限循环中)所以实际上代码甚至不会调用 B
。
请注意,即使创建成功,代码也永远不会完成 WaitAll
,因为 A
仍然处于无限循环中。
如果你想要 "run in parallel" 的方法,你需要 运行 它们 implicitly/explicitly 在新线程上(即 Task.Run
或 Thread.Start
)或I/O 绑定调用 let 方法释放线程 await
.
简答
不,正如您编写的两个 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.Run
强制执行它, Thread
,等等。在这种情况下,使用 async
是完全不相关的)
async
修饰符不是神奇的在这里生成线程标记。它的唯一目的是让编译器知道一个方法 可能 依赖于一些异步操作(一个复杂的数据处理线程,I/O...)所以它必须设置一个状态机来协调那些异步操作产生的回调。
要在另一个线程上创建 A
运行,您可以使用 Task.Run 调用它,它使用 Task 对象包装新线程上的调用,您可以等待该对象。请注意,await
-ing 方法并不意味着您的代码 运行 与 A
的执行并行:它会一直到您 await
的那一行Task 对象,告诉编译器您需要 Task 对象return 的值。在这种情况下 await
-ing Task.Run(A)
将有效地使您的程序永远 运行,等待 A
到 return,这是永远不会发生的事情(除非计算机出现故障).
请记住,将方法标记为异步但实际上不等待任何操作只会产生编译器警告的效果。如果您 await 不是真正异步的东西(它 return 立即在调用线程上使用 Task.FromResult 之类的东西),这将意味着您的程序需要 运行 时间速度惩罚。然而,它非常轻微。
在下面的代码中,在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());
}
}
}
不,显示的方法预计不会 "run in parallel"。
为什么从未调用 B
- 你有任务列表 tasks
通过本质上一系列 .Add
调用构建 - 第一个是 result[=35=添加了 A()
的 ]。由于 A
方法没有任何 await
它将 运行 在同一个线程上同步完成。然后 B()
会被调用。
现在 A
永远不会完成(它处于无限循环中)所以实际上代码甚至不会调用 B
。
请注意,即使创建成功,代码也永远不会完成 WaitAll
,因为 A
仍然处于无限循环中。
如果你想要 "run in parallel" 的方法,你需要 运行 它们 implicitly/explicitly 在新线程上(即 Task.Run
或 Thread.Start
)或I/O 绑定调用 let 方法释放线程 await
.
简答
不,正如您编写的两个 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.Run
强制执行它,Thread
,等等。在这种情况下,使用async
是完全不相关的)
async
修饰符不是神奇的在这里生成线程标记。它的唯一目的是让编译器知道一个方法 可能 依赖于一些异步操作(一个复杂的数据处理线程,I/O...)所以它必须设置一个状态机来协调那些异步操作产生的回调。
要在另一个线程上创建 A
运行,您可以使用 Task.Run 调用它,它使用 Task 对象包装新线程上的调用,您可以等待该对象。请注意,await
-ing 方法并不意味着您的代码 运行 与 A
的执行并行:它会一直到您 await
的那一行Task 对象,告诉编译器您需要 Task 对象return 的值。在这种情况下 await
-ing Task.Run(A)
将有效地使您的程序永远 运行,等待 A
到 return,这是永远不会发生的事情(除非计算机出现故障).
请记住,将方法标记为异步但实际上不等待任何操作只会产生编译器警告的效果。如果您 await 不是真正异步的东西(它 return 立即在调用线程上使用 Task.FromResult 之类的东西),这将意味着您的程序需要 运行 时间速度惩罚。然而,它非常轻微。