当我取消注释
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 后控制流去往何处?
抱歉造成混乱。
当我有东西要测试时,我只写 foo、bar、baz,完成后评论
,编写新的 foo,复制前一个的一些方法等等。
我将
foo.DoAsync
视为 Func<Task>
,尽管现在是 Task
。await foo.DoAsync
,await foo.DoAsync()
如果可以的话,我可以再问一个问题吗?
lambda 是否捕获最外层实例?
顺便说一句,谢谢之前的回复。
public static async Task ActualAsync()
{
var foo = new Foo();
Console.WriteLine("************** Actual begin **************");
var task = Task.Run(async () =>
{
// Causes a deadlock because lambda didn't caputer the foo.ATask before it's overwritten.
Console.WriteLine($"a: {await foo.ATask} expecting 1");
Console.WriteLine($"b: {await foo.BTask} expecting 2");
return 3;
});
// Give time to Task.Run so it can run before foo.ATask is overwritten.
//await Task.Delay(1000);
foo.ATask = task;
await foo.ATask;
Console.WriteLine("************** Actual end **************");
}
internal class Foo
{
public Task<int> ATask = Task.FromResult(1);
public Task<int> BTask = Task.FromResult(2);
}
我以为lambda会单独捕获变量。
public static async Task ExpectedAsync()
{
var foo = new Foo();
var lambdaInstance = new FooLambdaExpected()
{
ATask = foo.ATask,
BTask = foo.BTask
};
Console.WriteLine("************** Expected begin ************");
var task = Task.Run(lambdaInstance.LambdaMethod);
foo.ATask = task;
await foo.ATask;
Console.WriteLine("************** Expected end ************");
}
internal class FooLambdaExpected
{
public Task<int> ATask { get; set; } = null!;
public Task<int> BTask { get; set; } = null!;
public async Task<int> LambdaMethod()
{
Console.WriteLine($"a: {await ATask} expecting 1");
Console.WriteLine($"b: {await BTask} expecting 2");
return 3;
}
}
我想它会捕获
foo
本身。
public static async Task AssumedAsync()
{
var foo = new Foo();
var lambdaInstance = new FooLambdaAssumed()
{
Foo = foo
};
Console.WriteLine("************** Assumed begin ***********");
var task = Task.Run(lambdaInstance.LambdaMethod);
// Give time to Task.Run so it can run before foo.ATask is overwritten.
//await Task.Delay(1000);
foo.ATask = task;
await foo.ATask;
Console.WriteLine("************** Assumed end ***********");
}
internal class FooLambdaAssumed
{
public Foo Foo { get; set; } = null!;
public async Task<int> LambdaMethod()
{
Console.WriteLine($"a: {await Foo.ATask} expecting 1");
Console.WriteLine($"b: {await Foo.BTask} expecting 2");
return 3;
}
}
由
Task
创建的 Task.Run
仅运行一次。每次您await
它都不会再次运行。这是一个常见的误解,在我学习异步编程时也让我措手不及。
await
语句是被动操作。它只是等待任务完成,除非任务已经完成,在这种情况下它会立即继续执行下一条语句。 await
不会影响任务的状态。如果任务尚未开始,则不会导致任务执行;如果任务已经完成,则不会导致任务重新启动。