tags:

views:

2144

answers:

3

I was going to post a question, but figured it out ahead of time and decided to post the question and the answer - or at least my observations.

When using an anonymous delegate as the WaitCallback, where ThreadPool.QueueUserWorkItem is called in a foreach loop, it appears that the same one foreach-value is passed into each thread.

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 ); 
            }
        }
 }

For a collection of 16 Thing instances in Things I observed that each 'Thing' passed to WorkWithOneThing corresponded to the last item in the 'things' list.

I suspect this is because the delegate is accessing the 't' outer variable. Note that I also experimented with passing the Thing as a parameter to the anonymous delegate, but the behavior remained incorrect.

When I re-factored the code to use a named WaitCallback method and passed the Thing 't' to the method, viola ... the i'th instance of Things was correctly passed into WorkWithOneThing.

A lesson in parallelism I guess. I also imagine that the Parallel.For family addresses this, but that library was not an option for us at this point.

Hope this saves someone else some time.

Howard Hoffman

+4  A: 

This is correct, and describes how C# captures outside variables inside closures. It's not directly an issue about parallelism, but rather about anonymous methods and lambda expressions.

This question discusses this language feature and its implications in detail.

mquander
This article was also helpful: http://www.managed-world.com/archive/2008/06/13/lambdas---know-your-closures.aspx
Howard Hoffman
+1  A: 

This is a common occurrence when using closures and is especially evident when constructing LINQ queries. The closure references the variable, not its contents, therefore, to make your example work, you can just specify a variable inside the loop that takes the value of t and then reference that in the closure. This will ensure each version of your anonymous delegate references a different variable.

Jeff Yates
+1  A: 

Below is a link detailing why that happens. It's written for VB but C# has the same semantics.

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

JaredPar