lambda 是否捕获最外层实例?

问题描述 投票:0回答:1

当我取消注释

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;
    }
}
c# .net multithreading asynchronous lambda
1个回答
1
投票

Task
创建的
Task.Run
仅运行一次。每次您
await
它都不会再次运行。这是一个常见的误解,在我学习异步编程时也让我措手不及。

await
语句是被动操作。它只是等待任务完成,除非任务已经完成,在这种情况下它会立即继续执行下一条语句。
await
不会影响任务的状态。如果任务尚未开始,则不会导致任务执行;如果任务已经完成,则不会导致任务重新启动。

© www.soinside.com 2019 - 2024. All rights reserved.