Is there any problem to use anonymous methods under multi-thread?

  • Thread starter Thread starter Dancefire
  • Start date Start date
D

Dancefire

Hi,

I tried the code below:

for(int i = 0; i < 10; ++i)
{
int a = i;
Thread task = new Thread(delegate()
{
Console.WriteLine(string.Format("Thread start: a = {0}, i = {1}",
a, i));
});
task.Start();
}

And the output is

Thread start: a = 0, i = 2
Thread start: a = 2, i = 2
Thread start: a = 1, i = 2
Thread start: a = 3, i = 3
Thread start: a = 4, i = 5
Thread start: a = 5, i = 6
Thread start: a = 6, i = 7
Thread start: a = 7, i = 7
Thread start: a = 8, i = 9
Thread start: a = 9, i = 10

It looks like a race condition problem.

What is the problem? why i got such result? How can I make it correct
without pass parameter, such as not using ParameterizedThreadStart in
this case?
Is there any guide to use anonymous methods under multi-thread
environment?
 
Dancefire said:
I tried the code below:

for(int i = 0; i < 10; ++i)
{
int a = i;
Thread task = new Thread(delegate()
{
Console.WriteLine(string.Format("Thread start: a = {0}, i = {1}",
a, i));
});
task.Start();
}

And the output is

Thread start: a = 0, i = 2
Thread start: a = 2, i = 2
Thread start: a = 1, i = 2
Thread start: a = 3, i = 3
Thread start: a = 4, i = 5
Thread start: a = 5, i = 6
Thread start: a = 6, i = 7
Thread start: a = 7, i = 7
Thread start: a = 8, i = 9
Thread start: a = 9, i = 10

It looks like a race condition problem.

Well, only sort of. "i" has been captured.
What is the problem? why i got such result?

You're using the same "i" variable, which has been captured, in each
thread. See http://pobox.com/~skeet/csharp/csharp2/delegates.html for
more details (or buy my book at http://manning.com/skeet - chapter 5
goes into a lot of details on this).
How can I make it correct without pass parameter, such as not
using ParameterizedThreadStart in this case?

If you take a copy of the value using a newly instantiated variable -
as indeed you do with the "a" variable - then you'll get the value of i
as the body of the loop was entered. If you use "i" directly within the
anonymous method, you'll get the "current" value of "i", which will
depend on whether the new thread executes before the loop continues.
Is there any guide to use anonymous methods under multi-thread
environment?

Yes - be careful about what you capture! That's a general comment, to
be honest - it certainly applies for more than just threading.
 
[...]
It looks like a race condition problem.

I suppose it sort of is. I wouldn't call it a "problem" so much as a
specific behavior. If you don't understand anonymous methods well
enough though, I can see how you might think it's a problem.
What is the problem? why i got such result? How can I make it correct
without pass parameter, such as not using ParameterizedThreadStart in
this case?
Is there any guide to use anonymous methods under multi-thread
environment?

Using an anonymous method with a thread is pretty much the same as
using regular methods with a thread. You simply need to keep in mind
that when an anonymous method uses a variable declared outside of the
thread, that variable is "captured". In other words, the variable
winds up being stored in a new compiler-generated class so that its
lifetime can match that of the anonymous method's instance.

In your example, both "a" and "i" are captured. There's only one
instance of "i", but you have a new instance of "a" for each iteration
in the loop and thus also for each execution of your anonymous method.

In the output, the value of "a" will reliably tell you which iteration
of the loop produced the particular thread being executed. But since
there is only one instance of "i", it will have whatever value it has
at the moment of the execution of the thread relative to the iteration
of the loop, since all of the threads are using the same instance of
"i". If for some reason none of the threads got to execute until the
loop was completed, all of the threads would output 10 for the value of
"i".

That's why "i" increases monotonically: the value of "i" is contiuously
increasing as the loop proceeds, with one or more of the
already-created threads getting a chance to run and display the current
value of "i".

The value of "a" is tied directly to each thread, since there's a
different instance of "a" for each thread. And you can see from the
output that most of your threads get run in the same order in which
they were created (but for some reason the second thread wound up at
the end of the order for the first three threads that got to execute
before it was the main thread's turn again).

One solution to the problem you appear to be having is exactly what
you've already done: provide a local variable that is a new instance
for each instance of your thread, and the value will be reliably tied
to the thread. Of course, you can also just pass the value as a
parameter to the anonymous method. Assuming you don't have any
existing requirements as to the parameters for the delegate, this
should be a fine solution as well.

Pete
 
Well, only sort of. "i" has been captured.


You're using the same "i" variable, which has been captured, in each
thread. Seehttp://pobox.com/~skeet/csharp/csharp2/delegates.htmlfor
more details (or buy my book athttp://manning.com/skeet- chapter 5
goes into a lot of details on this).


If you take a copy of the value using a newly instantiated variable -
as indeed you do with the "a" variable - then you'll get the value of i
as the body of the loop was entered. If you use "i" directly within the
anonymous method, you'll get the "current" value of "i", which will
depend on whether the new thread executes before the loop continues.


Yes - be careful about what you capture! That's a general comment, to
be honest - it certainly applies for more than just threading.

Thank Peter and Jon, I use "a" as a local copy of the "i", but I was
not sure about "a" is 'unique' for each thread. But now I understand,
it's local, for each anonymous, it was "captured" once, that is, there
is 10 "a" for each thread in this case.

Use local copy instead of use "global" one if the global one might be
changed out of the loop/ after the scope.

Thanks.
 
Back
Top