我打算发一个问题,但提前想出来并决定发布问题和答案 - 或者至少是我的意见。
当使用匿名委托作为WaitCallback时,在foreach循环中调用ThreadPool.QueueUserWorkItem时,似乎将相同的一个foreach-value传递到每个线程。
List< Thing > things = MyDb.GetTheThings();
foreach( Thing t in Things)
{
localLogger.DebugFormat( "About to queue thing [{0}].", t.Id );
ThreadPool.QueueUserWorkItem(
delegate()
{
try
{
WorkWithOneThing( t );
}
finally
{
Cleanup();
localLogger.DebugFormat("Thing [{0}] has been queued and run by the delegate.", t.Id );
}
});
}
对于Things中的16个Thing实例的集合,我观察到传递给WorkWithOneThing的每个'Thing'对应于'things'列表中的最后一个项目。
我怀疑这是因为委托正在访问't'外部变量。请注意,我还尝试将Thing作为参数传递给匿名委托,但行为仍然不正确。
当我重新考虑代码以使用命名的WaitCallback方法并将Thing't传递给方法时,瞧......事物的第一个实例被正确地传递给了WorkWithOneThing。
我猜想并行课程。我还想象Parallel.For系列可以解决这个问题,但此时我们不能选择该库。
希望这能节省一些时间。
霍华德霍夫曼答案 0 :(得分:7)
这是正确的,并描述了C#如何捕获闭包内的变量。这不是关于并行性的直接问题,而是关于匿名方法和lambda表达式的问题。
This question详细讨论了这种语言特征及其含义。
答案 1 :(得分:1)
这在使用闭包时很常见,在构造LINQ查询时尤其明显。因此,闭包引用变量而不是其内容,以使您的示例工作,您可以在循环内指定一个变量,该变量取t的值,然后在闭包中引用该变量。这将确保您的匿名委托的每个版本引用不同的变量。
答案 2 :(得分:1)
下面是一个详细说明发生这种情况的链接。它是为VB编写的,但C#具有相同的语义。
http://blogs.msdn.com/jaredpar/archive/2007/07/26/closures-in-vb-part-5-looping.aspx