C# 对.NET ThreadPool.QueueUserWorkItem使用匿名委托

C# 对.NET ThreadPool.QueueUserWorkItem使用匿名委托,c#,closures,C#,Closures,我本来想发布一个问题,但提前想好了,决定发布问题和答案——或者至少是我的观察结果 当使用匿名委托作为WaitCallback(其中在foreach循环中调用ThreadPool.QueueUserWorkItem)时,似乎会向每个线程传递相同的foreach值 List< Thing > things = MyDb.GetTheThings(); foreach( Thing t in Things) { localLogger.DebugFormat( "About to

我本来想发布一个问题,但提前想好了,决定发布问题和答案——或者至少是我的观察结果

当使用匿名委托作为WaitCallback(其中在foreach循环中调用ThreadPool.QueueUserWorkItem)时,似乎会向每个线程传递相同的foreach值

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 ); 
            }
        });
 }
Listthings=MyDb.GetTheThings();
foreach(事物中的事物)
{
DebugFormat(“即将对事物进行排队[{0}]”,t.Id);
ThreadPool.QueueUserWorkItem(
代表()
{
尝试
{
使用一件物品(t);
}
最后
{
清理();
DebugFormat(“Thing[{0}]已排队并由委托运行。”,t.Id);
}
});
}
对于Things中16个Thing实例的集合,我观察到传递给WorkWithOneThing的每个“Thing”都对应于“Things”列表中的最后一项

我怀疑这是因为委托正在访问“t”外部变量。请注意,我还尝试将该对象作为参数传递给匿名委托,但行为仍然不正确

当我重新分解代码以使用命名的WaitCallback方法并将't'传递给该方法时,瞧。。。第i个实例被正确地传递到WorkWithOneThing中

我想这是并行性的一课。我还认为Parallel.For family解决了这个问题,但在这一点上,这个库不是我们的选择

希望这能为其他人节省一些时间


Howard Hoffman

这是正确的,并描述了C#如何在闭包中捕获外部变量。这不是关于并行性的直接问题,而是关于匿名方法和lambda表达式的问题


详细讨论此语言功能及其含义。

这是使用闭包时常见的情况,在构造LINQ查询时尤其明显。闭包引用的是变量,而不是它的内容,因此,为了使示例正常工作,您可以在循环中指定一个变量,该变量的值为t,然后在闭包中引用该变量。这将确保匿名代理的每个版本引用不同的变量。

下面的链接详细说明了发生这种情况的原因。它是为VB编写的,但C#具有相同的语义


这篇文章也很有帮助:如果您试图编译这段代码,您不会得到一个错误“System.Threading.WaitCallback”不接受“0”参数,因为您没有指定任何参数-请尝试将上述声明从:delegate(){…}更改为delegate{…},这是我在进行更改之前遇到的问题。希望这对你有帮助。