等待手动创建的任务不考虑内部等待

时间:2019-10-17 19:11:10

标签: c# async-await task

我使用异步和等待遇到了奇怪的行为。如果我尝试等待手动创建的任务T1,而该任务本身应该等待另一个任务T2,则即使任务T2仍在等待,任务T1也已经运行完毕。

为了说明这个问题,我写了一些代码。当我从此示例运行“ task1”时,输出为:

  

task1运行完毕   正在更新...

当我改为运行'task2'时,输出始终为:

  

更新中...   task2运行完毕

有人解释为什么第一个等待表达式不依赖于内部等待吗?

private void OnLoaded(object sender, RoutedEventArgs e)
{
    var task1 = new Task(async () => await UpdateAfterDelay());
    task1.Start();
    await task1;
    Console.WriteLine("task1 ran to completion");

    var task2 = Task.Run(async () => await UpdateAfterDelay());
    await task2;
    Console.WriteLine("task2 ran to completion");
}

private async Task UpdateAfterDelay()
{
    await Task.Delay(2000);
    Console.WriteLine("updating...");
}

3 个答案:

答案 0 :(得分:4)

Daniel有正确的想法。如果您查看constructors for Task,它们都只接受Action对象,它们实际上是void方法。这意味着您的匿名方法被解释为async void,即“即发即弃”(启动它,别等它了)。

如果您不使用匿名方法,这将变得更加清楚:

var task1 = new Task(UpdateAfterDelayTask); //this does not compile

var task2 = new Task(UpdateAfterDelayVoid); //this does

private async Task UpdateAfterDelayTask()
{
    await Task.Delay(2000);
}

private async void UpdateAfterDelayVoid()
{
    await Task.Delay(2000);
}

task1分配抱怨您提供的方法的返回类型错误。

Task.Run,但是,has an overload接受Func<Task>(一种返回Task的方法)。因此,编译器检查您的匿名方法,发现它返回了Task,并选择了Func<Task>重载。 returns a new Task that depends on the Task returned by your anonymous method过载。

所有这些,也许是由于您未共享而使用new TaskTask.Run的原因,但实际上并不需要。您可以这样做:

var task1 = UpdateAfterDelay();
// do something else
await task1;

如果您不是在“做其他事情”,那么您根本不需要task1变量。只是await UpdateAfterDelay()

值得一提的是this article解释了为什么几乎不需要使用new Task(),而this later article(在Task.Run出来之后就写了)解释了为什么您几乎总是想要使用Task.Run()(如果需要的话)。

答案 1 :(得分:2)

我想这里发生的是,由于Task类不包含接受a的构造函数,因此传递给Action构造函数的异步lambda被隐式转换为TaskFunc<Task>,这就是您想要的。因此,它像是Action一样执行-并立即返回。在IntelliSense中检查构造函数中使用的类型以确认这一点。

答案 2 :(得分:-1)

其他答案都有正确的主意,但可以进行总结和简化:

使用

var task1 = new Task(...)
task1.Start();

使用异步lambda创建一个空任务,该任务在后台运行并立即返回(因此await task1实际上不需要等待)

但使用

var task2 = Task.Run(...)

创建一个仍在后台运行的等待任务,但实际上将返回一个await可以等待的值。