为什么使用"继续"按顺序运行任务不起作用?



我的目标是在"Task1"之后启动"Task2"。起初我在下面编写了类似"Code1"的代码,但它不起作用(Task2在Task1完成之前启动(。所以我搜索了 Stackoverflow 并按照现有答案的建议修改了我的代码,如下面的"Code2"。我想知道为什么"Code1"不起作用。

代码1

static void Main(string[] args)
{
var p = new Program();
p.Test2();
Console.ReadKey();
}
void Test2()
{
Task.Factory.StartNew(async () =>
{
await Task1();
}).ContinueWith((t) => {
Task2();
});
}
async Task Task1()
{
Debug.WriteLine("Task 1 starting....");
await LongTask();
Debug.WriteLine("Task 1 done");
}
Task LongTask()
{
return Task.Factory.StartNew(() =>
{
Thread.Sleep(3000);
});            
}
void Task2()
{
Debug.WriteLine("Task 2");
}

代码2

Task.Factory.StartNew(async () =>
{
await Task1();
Task2();
}).ContinueWith((t) => {
//Task2();
});

因为当你像Task.Factory.StartNew(async () => ...一样运行任务时,它会返回Task<Task>(任务的任务(。并且第一个任务在没有等待内部Task的情况下终止.

为了防止这种情况,您可以使用Unwrap方法:

Task.Factory.StartNew(async () =>
{
await Task1();
})
.Unwrap()
.ContinueWith((t) => {
Task2();
});

StartNew将返回Task<Task>,并且似乎您想在内部任务完成后执行延续。这就是为什么我们需要Unwrap. 它"解包"作为外部任务的结果返回的内部任务。 在任务上调用解包会给你一个新任务(我们通常称之为代理(,它表示内部任务的最终完成。然后,我们正在为内部任务添加延续。

但是,由于Task.Run会自动进行解包,因此在这种情况下您可以使用Task.Run

Task.Run(async () =>
{
await Task1();
})
.ContinueWith((t) => {
Task2();
});

这可以简化为:

Task.Run(async () =>
{
await Task1();
Task2();
});

有关Task.RunTask.Factory.StartNew之间差异的详细信息:

在此处阅读 Stephen Toub 的更多信息,他是 .Net 团队的工程师。Task.Run情况下的决定理由:

因为我们希望人们想要卸载工作是很常见的 到线程池,并且为了使用async/await的工作,我们决定 将此解包功能构建到Task.Run中。

顺便说一句,正如斯蒂芬建议的那样,在大多数情况下尝试使用Task.Run,但这绝不会过时Task.Factory.StartNew,仍然有一些地方必须使用Task.Factory.StartNew

Task.Factory.StartNew仍然有许多重要的(尽管更高级( 使用。 您可以控制任务方式的TaskCreationOptions行为。 您可以控制任务应在哪里的计划程序 排队并运行。 您可以使用接受对象的重载 状态,对于性能敏感的代码路径,可用于避免 闭包和相应的分配。 对于简单情况, 不过,Task.Run是你的朋友。

Task.Factory.StartNew不了解异步lambda。对于Task.Factory.StartNew您的 lambda 只是函数返回Task。它不会自动等待该任务。另外,请注意,在现代 C# 中不鼓励使用Task.Factory.StarNewContinueWith,因为它很难正确使用(请阅读 Stephen Cleary 的"StartNew is Dangerous"(。只有当你离不开它们时,你才应该使用Task.Factory.StarNewContinueWith

相反,您可以再次使用 async/await:

async Task Test2()
{
await Task1();
Task2();
}

Task.Run

async Task Test2()
{
return Task.Run(await () => 
{
await Task1();
Task2();
});
}

如果要确保异步Task1()在后台线程上启动,则第二种方法可能很方便。

最新更新