我的目标是在"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.Run
和Task.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.StarNew
和ContinueWith
,因为它很难正确使用(请阅读 Stephen Cleary 的"StartNew is Dangerous"(。只有当你离不开它们时,你才应该使用Task.Factory.StarNew
或ContinueWith
。
相反,您可以再次使用 async/await:
async Task Test2()
{
await Task1();
Task2();
}
或Task.Run
:
async Task Test2()
{
return Task.Run(await () =>
{
await Task1();
Task2();
});
}
如果要确保异步Task1()
在后台线程上启动,则第二种方法可能很方便。