C# 异步方法未并行运行

C# 异步方法未并行运行,c#,asynchronous,task-parallel-library,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 Tas

在下面的代码中,在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());
        }
    }
}
使用System.Collections.Generic;
使用系统诊断;
使用System.Threading.Tasks;
命名空间控制台应用程序1
{
班级计划
{
专用静态异步任务A()
{
对于(;;)
{
}
}
专用静态异步任务B()
{
Trace.TraceInformation(“B-启动”);
}
静态void Main(字符串[]参数)
{
var tasks=新列表{A(),B()};
Task.WaitAll(tasks.ToArray());
}
}
}

否,所示方法不应“并行运行”

为什么从不调用
B
-您有一个任务列表
任务
基本上是通过一系列
调用构建的。添加
调用-首先是添加
A()的结果。由于
A
方法没有任何
await
,因此它将在同一线程上同步运行到完成。然后调用
B()

现在
A
将永远不会完成(它处于无限循环中),所以实际上代码甚至不会到达对
B
的调用

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


如果希望方法“并行运行”,则需要在新线程上隐式/显式运行它们(即使用
Task.run
Thread.Start
),或者对于i/O绑定调用,让方法使用
wait

简短回答来释放线程

不,在编写两个
async
方法时,它们实际上并没有并行运行。添加
wait Task.Yield()到您的第一个方法(例如,在循环内部)将允许他们这样做,但有更合理和更直接的方法,这在很大程度上取决于您实际需要什么(在单个线程上交错执行?在多个线程上实际并行执行?)

长答案

首先,将函数声明为
async
并不一定会使它们异步运行。这相当于简化了语法,因此可以在此处阅读有关概念的更多信息:

实际上,
A
根本不是异步的,因为它的方法体中没有一个
wait
。第一次使用
之前的指令将等待
像常规方法一样同步运行

从那时起,您等待的对象决定接下来会发生什么,即剩余方法运行的上下文

若要强制在另一个线程上执行任务,请使用
task.Run
或类似命令

在这种情况下,添加
wait Task.Yield()
会起作用,因为当前同步上下文为
null
,这确实会导致任务计划程序(应该是
ThreadPoolTaskScheduler
)要在线程池线程上执行其余指令,某些环境或配置可能会导致您只有其中一个指令,因此仍无法并行运行

摘要

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

  • 并发(通过合理地使用
    async
    /
    wait
    来启用)和
  • 并行性(只有当并发任务以正确的方式调度时才会发生,或者如果您使用
    任务来强制执行它,则会发生。运行
    线程
    ,等等。在这种情况下,
    异步
    的使用无论如何都是完全无关的)

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

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


请记住,将方法标记为异步但不实际等待任何操作只会产生编译器警告的效果。如果您等待的内容不是真正异步的(它会在调用线程上立即返回类似的内容),这将意味着您的程序将受到运行时速度的惩罚。不过,这很轻微。

也许这是忙碌等待的结果。A()是否将CPU固定在100%?我有4个处理器。一个是100%。但是,是否应该以并发(并行)方式运行?@FernandoSilva请参阅
Task.Run
。仅添加
async
不会使它们并行。@FernandoSilva:此代码将导致编译器警告。请阅读;他们准确地解释了发生了什么。这是真的,但这是一个非常不完整的答案。你不应该删除它,它在它的当前表单上添加了非常重要的信息。