为什么 Task.Factory.StartNew 立即返回,而 Task.Run 不会?

10

考虑以下代码片段:

Task[] tasks = new Task[4];
for (var i = 0; i < tasks.Length; ++i) {
    tasks[i] = Task.Run(async () =>
    {
        await Task.Delay(4000);
    });
}
for (var i = 0; i < tasks.Length; ++i)
    await tasks[i];

Console.WriteLine("Done!");

这段代码正常运行,执行时间为4.000毫秒。然而,如果我将Task.Run替换为Task.Factory.StartNew,它只需要0.006毫秒!

有人能解释一下为什么吗?

3个回答

10

有人能解释一下为什么吗?

简单来说,StartNew 不理解 async 委托。

所以,当你的委托在第一个 await 时返回一个未完成的任务, StartNew 看到委托退出并认为其工作已完成。这就是为什么它在此处返回一个 Task<Task>Task.Run 具有处理异步委托的特殊逻辑,自动展开内部任务。

这只是使用 StartNew 处理异步代码时的其中一个陷阱;我在我的博客文章 StartNew is Dangerous 中详细介绍了这些问题。


8

好的,我在阅读了https://blogs.msdn.microsoft.com/pfxteam/2011/10/24/task-run-vs-task-factory-startnew/后自己找到了答案。

通过在这里使用async关键字,编译器将把此委托映射为一个Func<Task<int>>:调用该委托将返回表示此调用的最终完成的Task<int>。并且由于该委托是Func<Task<int>>,因此TResultTask<int>,因此“t”的类型将是Task<Task<int>>而不是Task<int>

因此,这段代码按预期工作:

Task[] tasks = new Task[4];
for (var i = 0; i < tasks.Length; ++i) {
    tasks[i] = Task.Factory.StartNew(async () =>
    {
        await Task.Delay(4000);
    });
}
for (var i = 0; i < tasks.Length; ++i)
    await await (tasks[i] as Task<Task>);

Console.WriteLine("Done!");

也可以使用Unwrap来实现:

Task[] tasks = new Task[4];
for (var i = 0; i < tasks.Length; ++i) {
    tasks[i] = Task.Factory.StartNew(async () =>
    {
        await Task.Delay(4000);
    }).Unwrap();
}
for (var i = 0; i < tasks.Length; ++i)
    await tasks[i];

Console.WriteLine("Done!");

3
答案在这里
有一个关于两种方法行为差异的问题: Task.Run(Action)默认不允许使用TaskCreationOptions.AttachedToParent选项启动的子任务附加到当前任务实例,而StartNew(Action)则可以。
因此,Task.Run会等待执行完成,而Task.Factory.StartNew会立即返回一个任务。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接