Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Using Anonymous Delegates with .NET ThreadPool.QueueUserWorkItem

Tags:

closures

c#

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, voilà ... 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

like image 227
Howard Hoffman Avatar asked Mar 05 '09 21:03

Howard Hoffman


3 Answers

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.

like image 111
mqp Avatar answered Sep 17 '22 23:09

mqp


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.

like image 27
Jeff Yates Avatar answered Sep 19 '22 23:09

Jeff Yates


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

like image 25
JaredPar Avatar answered Sep 20 '22 23:09

JaredPar