且构网

分享程序员开发的那些事...
且构网 - 分享程序员编程开发的那些事

如何在 lambda 表达式中捕获外部变量的值?

更新时间:2023-11-11 12:01:10

这更多地与 lambda 相关,而不是线程.lambda 捕获对变量的引用,而不是变量的值.这意味着当您尝试在代码中使用 i 时,它的值将是最后存储在 i 中的任何值.

This has more to do with lambdas than threading. A lambda captures the reference to a variable, not the variable's value. This means that when you try to use i in your code, its value will be whatever was stored in i last.

为避免这种情况,您应该在 lambda 启动时将变量的值复制到局部变量.问题是,启动任务有开销,第一个副本可能只有在循环完成后才能执行.下面的代码也会失败

To avoid this, you should copy the variable's value to a local variable when the lambda starts. The problem is, starting a task has overhead and the first copy may be executed only after the loop finishes. The following code will also fail

for (var i = 0; i < 50; ++i) {
    Task.Factory.StartNew(() => {
        var i1=i;
        Debug.Print("Error: " + i1.ToString());
    });
}

正如 James Manning 所指出的,您可以在循环中添加一个局部变量并将循环变量复制到那里.这样你就创建了 50 个不同的变量来保存循环变量的值,但至少你得到了预期的结果.问题是,你确实得到了很多额外的分配.

As James Manning noted, you can add a variable local to the loop and copy the loop variable there. This way you are creating 50 different variables to hold the value of the loop variable, but at least you get the expected result. The problem is, you do get a lot of additional allocations.

for (var i = 0; i < 50; ++i) {
    var i1=i;
    Task.Factory.StartNew(() => {
        Debug.Print("Error: " + i1.ToString());
    });
}

***的解决方案是将循环参数作为状态参数传递:

The best solution is to pass the loop parameter as a state parameter:

for (var i = 0; i < 50; ++i) {
    Task.Factory.StartNew(o => {
        var i1=(int)o;
        Debug.Print("Error: " + i1.ToString());
    }, i);
}

使用状态参数会减少分配.查看反编译的代码:

Using a state parameter results in fewer allocations. Looking at the decompiled code:

  • 第二个代码段将创建 50 个闭包和 50 个委托
  • 第三个代码段将创建 50 个装箱整数,但只有一个委托