Anonymous Methods As ThreadStarts

D

d225563

I read an article that had a really elegant solution to pass parameters
to a thread by using an anonymous method as your ThreadStart. It
seemed pretty slick and even worked when I tried it. However, with
some more playing around, I got some really strange results. Using the
code below, sometimes my output is 2 and 3. Sometimes, it's 3 and 3.
However, it's never been 1 and 2 which is what I would expect. Can
anybody set me straight on why I'm getting the unexpected results? My
suspicion is that somehow my integer is getting passed by reference
rather than value, but I'd like someone else to corroborate that before
I go on believing it. (Note: I know about ParameterizedThreadStart.
I'm just trying to use this example to understand anonymous methods a
little better.)

namespace AnonymousConfusion
{
class Program
{
static void Main(string[] args)
{
int i = 1;

ThreadStart ts1 = delegate { WorkThread(i); };
Thread t1 = new Thread(ts1);
// i should be 1 when i'm starting my thread, right?
t1.Start();

i++;

ThreadStart ts2 = delegate { WorkThread(i); };
Thread t2 = new Thread(ts2);
// i should 2 when i'm starting my thread, right?
t2.Start();

i++;

t1.Join();
t2.Join();

// why am i getting 2 and 3 or sometimes 3 and 3 for
output?
Console.WriteLine("Done");
Console.ReadLine();
}

static void WorkThread(int i)
{
Console.WriteLine(i.ToString());
}
}
}
 
K

Kevin Spencer

This has nothing to do with Anonymous methods, other than the fact that you
used an anonymous method to create the threads.

The nature of threads is that they run independently of each other and of
the parent thread. Therefore, the time it takes for each thread to process
is in no way related to the order in which they are created.

--
HTH,

Kevin Spencer
Microsoft MVP
..Net Developer

Presuming that God is "only an idea" -
Ideas exist.
Therefore, God exists.
 
M

Marc Gravell

This makes perfect sense; the "i" in the anonymous delegate (I'm not on
about WorkThread here, just the bit in the braces) is the same i - and it
all depends on timing:

[thread 1] i=1
[thread 1] go to start a thread
[thread 1] increment i (now = 2)
[thread 2] catches up, call WorkThread with i, currently 2
[thread 1] go to start a thread
[thread 1] increment i (now = 3)
[thread 3] catches up, call WorkThread with i, currently 3

or

[thread 1] i=1
[thread 1] go to start a thread
[thread 1] increment i (now = 2)
[thread 1] go to start a thread
[thread 1] increment i (now = 3)
[thread 2] catches up, call WorkThread with i, currently 3
[thread 3] catches up, call WorkThread with i, currently 3

In a way, you have been lucky; this type of usage could also lead to
completely phantom reads of i, particularly if i is of a larger data-type
(long etc); for this (latter) reason you should *always* sync access to
variables when using multiple threads.

But the main behaviour makes perfect sense

Marc
 
?

=?iso-8859-1?Q?Lasse=20V=e5gs=e6ther=20Karlsen?=

I read an article that had a really elegant solution to pass
parameters to a thread by using an anonymous method as your
ThreadStart. It seemed pretty slick and even worked when I tried it.
However, with some more playing around, I got some really strange
results. Using the code below, sometimes my output is 2 and 3.
Sometimes, it's 3 and 3. However, it's never been 1 and 2 which is

If you take a look using Reflector or some other disassembly tool you'll
notice that a method that declares an anonymous method and has local variables
that is used in that method will have some "magic" code in it. Basically
what happens is that a class to hold the method is defined, and the local
variables in your outer method is stored in that class. When the method starts,
an object is constructed from that class and used internally. As such, your
thread(s) and outer method shares the same variables with each other. This
means that you get all the features of sharing variables easily and all the
headache when doing so with threads.

Let me show you an actual example:

public void Test()
{
String s = String.Empty;
System.Threading.ThreadStart ts = delegate
{
s = "Set in delegate";
};
ts();
System.Diagnostics.Debug.WriteLine(s);
}

The decompiled version of this looks like this:

public void Test()
{
MainForm.<>c__DisplayClass1 class1 = new MainForm.<>c__DisplayClass1();
class1.s = string.Empty;
ThreadStart start1 = new ThreadStart(class1.<Test>b__0);
start1();
Debug.WriteLine(class1.s);
}

and then:

[CompilerGenerated]
private sealed class <>c__DisplayClass1
{
// Methods
public <>c__DisplayClass1() { }
public void <Test>b__0()
{
this.s = "Set in delegate";
}

// Fields
public string s;
}

So, as Kevin noted, the threads all work on the same variable and might not
even get scheduled some time before you increment the variable and start
the next thread, and that's why you get those results.
 
J

Jon Skeet [C# MVP]

I read an article that had a really elegant solution to pass parameters
to a thread by using an anonymous method as your ThreadStart. It
seemed pretty slick and even worked when I tried it. However, with
some more playing around, I got some really strange results. Using the
code below, sometimes my output is 2 and 3. Sometimes, it's 3 and 3.
However, it's never been 1 and 2 which is what I would expect. Can
anybody set me straight on why I'm getting the unexpected results? My
suspicion is that somehow my integer is getting passed by reference
rather than value, but I'd like someone else to corroborate that before
I go on believing it.

It's not that it's being "passed" so much as that it's being shared
between the delegates. It's a captured variable - not really a local
variable any more.

See http://www.pobox.com/~skeet/csharp/csharp2/delegates.html for more
information and a really scary example...

Jon
 
W

Willy Denoyette [MVP]

|I read an article that had a really elegant solution to pass parameters
| to a thread by using an anonymous method as your ThreadStart. It
| seemed pretty slick and even worked when I tried it. However, with
| some more playing around, I got some really strange results. Using the
| code below, sometimes my output is 2 and 3. Sometimes, it's 3 and 3.
| However, it's never been 1 and 2 which is what I would expect. Can
| anybody set me straight on why I'm getting the unexpected results? My
| suspicion is that somehow my integer is getting passed by reference
| rather than value, but I'd like someone else to corroborate that before
| I go on believing it. (Note: I know about ParameterizedThreadStart.
| I'm just trying to use this example to understand anonymous methods a
| little better.)
|
| namespace AnonymousConfusion
| {
| class Program
| {
| static void Main(string[] args)
| {
| int i = 1;
|
| ThreadStart ts1 = delegate { WorkThread(i); };
| Thread t1 = new Thread(ts1);
| // i should be 1 when i'm starting my thread, right?
| t1.Start();
|
| i++;
|
| ThreadStart ts2 = delegate { WorkThread(i); };
| Thread t2 = new Thread(ts2);
| // i should 2 when i'm starting my thread, right?
| t2.Start();
|
| i++;
|
| t1.Join();
| t2.Join();
|
| // why am i getting 2 and 3 or sometimes 3 and 3 for
| output?
| Console.WriteLine("Done");
| Console.ReadLine();
| }
|
| static void WorkThread(int i)
| {
| Console.WriteLine(i.ToString());
| }
| }
| }
|

Before your thread gets actually a chance to run and pick up the value of i,
your main thread will have incremented the value once maybe twice.

Willy.
 
W

William Stacey [MVP]

To get expected results, you could pass i instead of using local var capture.

Note that t2 could still complete before t1, but they should still have the expected i.



private void button3_Click(object sender, EventArgs e)

{

int i = 1;



Thread t1 = new Thread(WorkThread);

t1.Name = "t1";

t1.Start(i);



i++;



Thread t2 = new Thread(WorkThread);

t2.Name = "t2";

t2.Start(i);



i++;



t1.Join();

t2.Join();

}

static void WorkThread(object i)

{

Console.WriteLine("Thread:{0} Value:{1}", Thread.CurrentThread.Name, (int)i);

}
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Top