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

标签 c# asynchronous lambda closures task-parallel-library

我刚刚遇到了以下行为:

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

会导致一系列“错误:x”,其中大部分x等于50。

类似地:

var a = "Before";
var task = new Task(() => Debug.Print("Using value: " + a));
a = "After";
task.Start();

将导致“使用值:之后”。

这显然意味着 lambda 表达式中的串联不会立即发生。在声明表达式时,如何在 lambda 表达式中使用外部变量的副本?以下不会更好地工作(这不一定是不连贯的,我承认):

var a = "Before";
var task = new Task(() => {
    var a2 = a;
    Debug.Print("Using value: " + a2);
});
a = "After";
task.Start();

最佳答案

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

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

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

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

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

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

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

使用状态参数可以减少分配。查看反编译代码:

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

关于c# - 如何捕获 lambda 表达式中外部变量的值?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/11049302/

相关文章:

c# - 我如何处理可能异步也可能不异步的接口(interface)方法?

javascript - 从 axios 调用中 react redux 设置初始状态

java - 当另一组 CompletableFuture 完成时,你如何完成一个 CompletableFuture?

c# - 如何让用户登录系统并仅在用户单击注销按钮后注销?

c# - 如何使用 OneDrive SDK 按文件哈希搜索

c# - 比较 LINQ 之前的代码

c++ - 为什么 C++17 中没有 std::future::then?

c# - 固定数组作为参数

c++ - 使用 C++14 lambda 测量任意函数的执行时间

java - Eclipse:构造函数未定义