使用.NET ThreadPool.QueueUserWorkItem的匿名代理

时间:2009-03-05 21:09:36

标签: c# closures

我打算发一个问题,但提前想出来并决定发布问题和答案 - 或者至少是我的意见。

当使用匿名委托作为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系列可以解决这个问题,但此时我们不能选择该库。

希望这能节省一些时间。

霍华德霍夫曼

3 个答案:

答案 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