You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

C#线程数组Lambda初始化启动异常:为何线程未全执行且索引异常?

问题根源:闭包捕获循环变量的经典陷阱

嗨,你遇到的这个问题是C#开发里非常常见的闭包陷阱,我来给你拆解得明明白白:

为什么会出现线程id为6、执行异常的情况?

你的循环里,变量i是在循环外部声明的,Lambda表达式() => EnterSemaphore(i)捕获的是这个变量的引用,而不是当前迭代时i的具体值。

当你调用testStart[i].Start()启动线程时,线程并不会立刻执行Lambda里的逻辑——它需要等待系统调度。而此时循环还在继续,i的值会不断自增。等循环结束时,i已经变成了6(因为循环条件是i <=5,最后一次循环后i会自增到6才退出)。

那些延迟执行的线程终于运行时,拿到的i都是最终的6,这就是为什么你会看到id为6的线程,且原本预期的1-5线程逻辑混乱的原因。

为什么另外两种方式能正常工作?

  • 直接用new Thread(EnterSemaphore).Start(i):这里Start(i)是把当前迭代的i值作为参数传递给线程,线程启动时会拿到这个值的副本,而非引用。每个线程都有独立的参数值,自然不受后续循环影响。
  • testStart[i].Join()Join()会让主线程等待当前线程完全执行完毕后再进入下一次循环。这意味着每次循环的i值还没被更新,线程就已经执行了Lambda代码,所以能拿到正确的i值。

怎么修复线程数组的启动方式?

解决方法很简单:在循环内部创建一个局部变量,保存当前i的值,让Lambda捕获这个局部变量。这样每个Lambda都会捕获一个独立的变量,对应循环当前迭代的i值。修改后的代码如下:

public SemaphoreLock() { 
    Thread[] testStart = new Thread[10]; 
    for (int i = 1; i <= 5; i++) { 
        // 创建局部变量保存当前i的值,避免闭包捕获同一个引用
        int localId = i;
        testStart[i] = new Thread(() => EnterSemaphore(localId)); 
        lock (StartLocker) 
            testStart[i].Start(); 
    } 
}

补充一句:你代码里的lock (StartLocker)对这个问题没有帮助,它只是保证线程启动操作的原子性,无法改变Lambda捕获变量的本质。

内容的提问来源于stack exchange,提问作者Wouter

火山引擎 最新活动