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

C# 等待手动创建的任务不考虑内部等待,c#,async-await,task,C#,Async Await,Task,我在使用async和Wait时遇到了一种奇怪的行为。如果我试图等待一个手动创建的任务T1,该任务本身应该等待另一个任务T2,那么即使任务T2仍在等待,任务T1也已经运行到完成 为了说明这个问题,我编写了一些代码。在本例中运行“task1”时,输出为: task1已运行完成 更新 当我运行“task2”时,输出总是: 更新。。。 任务2已运行完成 有人能解释为什么第一个等待表达式不依赖于内在的等待吗 private void OnLoaded(object sender, RoutedEventA

我在使用async和Wait时遇到了一种奇怪的行为。如果我试图等待一个手动创建的任务T1,该任务本身应该等待另一个任务T2,那么即使任务T2仍在等待,任务T1也已经运行到完成

为了说明这个问题,我编写了一些代码。在本例中运行“task1”时,输出为:

task1已运行完成 更新

当我运行“task2”时,输出总是:

更新。。。 任务2已运行完成

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

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...");
}

这里我假设传递到任务构造函数中的异步lambda被隐式转换为动作,因为任务类不包含接受Func的构造函数,而这正是您想要的。因此,它就像一个动作一样执行,并立即返回。检查IntelliSense中构造函数中使用的类型以确认这一点。

我假设这里发生的是,传递到任务构造函数中的异步lambda正在隐式转换为操作,因为任务类不包含接受Func的构造函数,而这正是您想要的。因此,它就像一个动作一样执行,并立即返回。检查IntelliSense中构造函数中使用的类型以确认这一点。

Daniel的想法是正确的。如果您查看,它们都只接受对象,这些对象本质上是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分配抱怨您给它的方法具有错误的返回类型

然而,接受Func的Task.Run是一个返回任务的方法。因此,编译器检查匿名方法,看到它返回一个任务,并选择Func重载。超负荷

尽管如此,也许你使用新任务或任务是有原因的。运行你没有共享的任务,但实际上你并不需要。您可以这样做:

var task1 = UpdateAfterDelay();
// do something else
await task1;
如果您没有执行其他操作,则根本不需要task1变量。延迟后等待更新


同样值得注意的是,这解释了为什么您几乎不需要使用新任务,并且是在任务之后编写的。Run的出现解释了为什么您几乎总是想使用Task。如果您需要,请运行。

Daniel的想法是正确的。如果您查看,它们都只接受对象,这些对象本质上是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分配抱怨您给它的方法具有错误的返回类型

然而,接受Func的Task.Run是一个返回任务的方法。因此,编译器检查匿名方法,看到它返回一个任务,并选择Func重载。超负荷

尽管如此,也许你使用新任务或任务是有原因的。运行你没有共享的任务,但实际上你并不需要。您可以这样做:

var task1 = UpdateAfterDelay();
// do something else
await task1;
如果您没有执行其他操作,则根本不需要task1变量。延迟后等待更新


同样值得注意的是,这解释了为什么您几乎不需要使用新任务,而是在任务之后编写。Run解释了为什么您几乎总是想使用Task。如果您需要,请运行。

其他答案都有正确的想法,但要总结和简化:

使用

var task1 = new Task(...)
task1.Start();
使用异步lambda创建一个在后台运行并立即返回的空任务,因此wait task1实际上不需要等待

但是使用

var task2 = Task.Run(...)

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

其他答案都有正确的想法,但要进行总结和简化:

使用

var task1 = new Task(...)
task1.Start();
使用异步lambda创建一个在后台运行并立即返回的空任务,因此wait task1实际上不需要等待

但是使用

var task2 = Task.Run(...)

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

由于您正在做非常奇怪的事情,因此您遇到了奇怪的行为。您已经有了一个返回任务的异步方法;为什么要经历所有这些使用异步lambda创建新任务的繁琐过程?如果你想要两个任务和两个等待,只需等待更新;等待更新;你完成了。你能解释一下为什么你试图构建这个过于复杂和混乱的工作流,而它可以简单地表达出来吗?任务构造函数不理解异步委托。这意味着它不认为Func是特殊的东西。它认为这项任务是公正的
正常返回值。因此,您最终会得到Task的返回值。在这些情况下,你必须分别等待外部和内部任务的完成,这是通过等待来实现的。因此,要使代码按预期运行,您必须进行两个小更改:1使用通用任务构造函数new Task,而不是非通用任务构造函数new Task。2等待两次所产生的任务。@Theodor Zoulias,这将起作用。谢谢但是推断类型不是Task,因为使用了await运算符。所以我仍然不明白为什么这不起作用。如果我在没有使用wait的情况下声明表达式,编译器会通知我调用未被等待,以便继续执行。这是没有意义的,因为使用await会导致相同的行为。我试图在一个方法中封装一大堆异步表达式。由于使用async/await需要将整个API设置为异步,因此总结而言,我无法避免将这些方法设置为异步,我正在尝试在不同的线程上执行计算密集型操作,并在这之后更新我的UI。但不管我的代码是什么,我现在对这两种方法为何表现不同很感兴趣。由于双方都在采取行动,我没想到会这样。如果我编写new Task=>UpdateAfterDelay,省略Wait运算符将导致编译器警告。您当前使用的是接受Action类型参数的非泛型任务构造函数。不是Func。因此,UpdateAfterDelay方法的返回值将被丢弃。这会成为一个问题,因为此方法的返回值是一个任务,而您无法等待此任务,因为没有返回对它的引用。因此,您的代码变得并行,被忽略的任务和task1之后的代码。开始并发运行。你可能认为等待任务1;等待前面提到的任务,但它不是。它等待着创建被忽略的内部任务的外部任务。因为你正在做非常奇怪的事情,所以你会遇到奇怪的行为。您已经有了一个返回任务的异步方法;为什么要经历所有这些使用异步lambda创建新任务的繁琐过程?如果你想要两个任务和两个等待,只需等待更新;等待更新;你完成了。你能解释一下为什么你试图构建这个过于复杂和混乱的工作流,而它可以简单地表达出来吗?任务构造函数不理解异步委托。这意味着它不认为Func是特殊的东西。它认为任务只是一个正常的返回值。因此,您最终会得到Task的返回值。在这些情况下,你必须分别等待外部和内部任务的完成,这是通过等待来实现的。因此,要使代码按预期运行,您必须进行两个小更改:1使用通用任务构造函数new Task,而不是非通用任务构造函数new Task。2等待两次所产生的任务。@Theodor Zoulias,这将起作用。谢谢但是推断类型不是Task,因为使用了await运算符。所以我仍然不明白为什么这不起作用。如果我在没有使用wait的情况下声明表达式,编译器会通知我调用未被等待,以便继续执行。这是没有意义的,因为使用await会导致相同的行为。我试图在一个方法中封装一大堆异步表达式。由于使用async/await需要将整个API设置为异步,因此总结而言,我无法避免将这些方法设置为异步,我正在尝试在不同的线程上执行计算密集型操作,并在这之后更新我的UI。但不管我的代码是什么,我现在对这两种方法为何表现不同很感兴趣。由于双方都在采取行动,我没想到会这样。如果我编写new Task=>UpdateAfterDelay,省略Wait运算符将导致编译器警告。您当前使用的是接受Action类型参数的非泛型任务构造函数。不是Func。因此,UpdateAfterDelay方法的返回值将被丢弃。这会成为一个问题,因为此方法的返回值是一个任务,而您无法等待此任务,因为没有返回对它的引用。因此,您的代码变得并行,被忽略的任务和task1之后的代码。开始并发运行。你可能认为等待任务1;等待前面提到的任务,但它不是。它等待创建被忽略的内部任务的外部任务。文章也不建议总是使用Task.Run。这两篇文章都介绍了更专门的方法的可能用途。@TheodorZoulias True。。。我澄清了这句话。斯蒂芬·克利里的观点比斯蒂芬·图布更为强烈:不要使用任务或任务构造函数-似乎是这样。即使Task.Run也像构造函数一样接受Action对象,它也可能以不同的方式处理它。但值得注意的是,在我的示例中没有Func类型
由于我将操作回调声明为异步方法,因此被终止。因此,如果我声明new Taskasync=>await UpdateAfterDelayTask,编译器将不会抱怨任何事情,并且可以预期任务是可等待的。@Dennis Kassel匿名异步委托被解释为Func或Func。这取决于=>之后的内容。在本例中,您指向一个异步void方法,因此异步委托被解释为Func。当新任务构造函数显示Func参数时,允许通过放弃返回值将其作为操作使用。将其与以下代码行进行比较:Math.Abs0;。这是允许的。这是完全无用的,因为Math.Abs方法没有副作用,但是你可以这样做。只是需要注意:这篇文章实际上并没有建议永远不要使用新任务。文章也不建议总是使用Task.Run。这两篇文章都介绍了更专门的方法的可能用途。@TheodorZoulias True。。。我澄清了这句话。斯蒂芬·克利里的观点比斯蒂芬·图布更为强烈:不要使用任务或任务构造函数-似乎是这样。即使Task.Run也像构造函数一样接受Action对象,它也可能以不同的方式处理它。但值得注意的是,在我的示例中,由于我将动作回调声明为异步方法,因此没有推断出类型Func。因此,如果我声明new Taskasync=>await UpdateAfterDelayTask,编译器将不会抱怨任何事情,并且可以预期任务是可等待的。@Dennis Kassel匿名异步委托被解释为Func或Func。这取决于=>之后的内容。在本例中,您指向一个异步void方法,因此异步委托被解释为Func。当新任务构造函数显示Func参数时,允许通过放弃返回值将其作为操作使用。将其与以下代码行进行比较:Math.Abs0;。这是允许的。因为Math.Abs方法没有副作用,所以它是完全无用的,但是您可以这样做;t、 开始;t、 等待;它肯定会导致调用线程等待。@TheodorZoulias.wait不同于waitreplace t.wait;带着等待t;。同样的道理。@Lamp:对于最初的问题,关键是传递给任务构造函数的委托是从异步lambda实例化的。委托调用会立即返回,因为它所做的第一件事就是等待。你的帖子根本没有提到这一关键方面,事实上,你所写的内容并不适用于任何一个不具备这一方面的场景。我看不出这有什么用处,更不用说对现有答案有什么补充了。因为你不明白我在回答原来的问题,所以加了四个字。上面的答案很好,但涵盖了更一般的情况,这使得答案更复杂。正如我所说的,我的答案是为了简化这个特定的案例;t、 开始;t、 等待;它肯定会导致调用线程等待。@TheodorZoulias.wait不同于waitreplace t.wait;带着等待t;。同样的道理。@Lamp:对于最初的问题,关键是传递给任务构造函数的委托是从异步lambda实例化的。委托调用会立即返回,因为它所做的第一件事就是等待。你的帖子根本没有提到这一关键方面,事实上,你所写的内容并不适用于任何一个不具备这一方面的场景。我看不出这有什么用处,更不用说对现有答案有什么补充了。因为你不明白我在回答原来的问题,所以加了四个字。上面的答案很好,但涵盖了更一般的情况,这使得答案更复杂。正如我所说,我的回答是为了简化这个具体案例。