当我取消注释
await Task.Delay(1_000);
时,foo.DoAsync
内的Task.Run
是Task.CompletedTask
,因为它等待Task.Run
开销。这样就可以正常运行了。
当我评论
await Task.Delay(1_000);
时,Task.Run中的foo.DoAsync
就是它本身。在调试模式下第一次命中断点 2 后,就不再命中断点 2。
internal class Foo
{
public Task DoAsync { get; set; }
public void Recursive()
{
// Breakpoint1
Recursive();
}
}
var foo = new Foo() { DoAsync = Task.CompletedTask };
//foo.Recursive();
Console.WriteLine($"Outer ThreadId: {Environment.CurrentManagedThreadId}");
var task = Task.Run(async () =>
{
Console.WriteLine($"Inner ThreadId: {Environment.CurrentManagedThreadId}");
// Breakpoint2
await foo.DoAsync;
Console.WriteLine("""
******
******
******
done
******
******
******
""");
});
await Task.Delay(1_000);
foo.DoAsync = task;
await foo.DoAsync;
我预计它会无限地击中断点 2,因为
foo.Recursive
将无限地击中断点 1,直到崩溃。首次命中断点 2 后控制流去往何处?
由
Task
创建的 Task.Run
仅运行一次。每次您await
它都不会再次运行。这是一个常见的误解,在我学习异步编程时也让我措手不及。
await
语句是被动操作。它只是等待任务完成,除非任务已经完成,在这种情况下它会立即继续执行下一条语句。 await
不会影响任务的状态。如果任务尚未开始,则不会导致任务执行;如果任务已经完成,则不会导致任务重新启动。