Using Anonymous Delegates with .NET ThreadPool.QueueUserWorkItem

I was about to ask a question, but guessed it, and decided to post the question and answer - or at least my observations.

When using an anonymous delegate like WaitCallback, where ThreadPool.QueueUserWorkItem is called in a foreach loop, it seems that the same foreach value is passed to 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 noticed that each "Thing" passed to WorkWithOneThing corresponds to the last item in the list of "things."

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

When I redefined the code to use the WaitCallback method and passed Thing 't' to the method, alt ... the ith instance of Things was correctly passed to WorkWithOneThing.

A lesson in parallelism I think. I also assume that the Parallel.For family is addressed to this, but this library was not an option for us at the moment.

Hope this saves you a little time.

Howard Hoffman

+6
closures c #
source share
3 answers

This is correct and describes how C # captures external variables inside closures. This is not a problem directly about parallelism, but rather about anonymous methods and lambda expressions.

This question discusses in detail this feature of the language and its consequences.

+7
source share

This is common when using closures and is especially noticeable when building LINQ queries. Therefore, a closure refers to a variable, not its contents, to make your example work, you can simply specify a variable inside the loop that takes the value t, and then refers to it in the closure. This ensures that each version of your anonymous delegate references a different variable.

+1
source share

Below is a link explaining why this is happening. It is 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

+1
source share

All Articles