6

我打算发布一个问题,但提前弄清楚并决定发布问题和答案 - 或者至少是我的观察结果。

当使用匿名委托作为 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' 传递给该方法时,瞧……Thing 的第 i 个实例被正确传递到 WorkWithOneThing。

我猜是并行性的一课。我还想象 Parallel.For 家族解决了这个问题,但此时该库不是我们的选择。

希望这可以节省其他人一些时间。

霍华德霍夫曼

4

3 回答 3

7

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

这个问题详细讨论了这种语言特性及其含义。

于 2009-03-05T21:11:32.903 回答
1

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

于 2009-03-05T21:13:12.230 回答
1

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

http://blogs.msdn.com/jaredpar/archive/2007/07/26/closures-in-vb-part-5-looping.aspx

于 2009-03-05T21:15:11.833 回答