Home All Groups Group Topic Archive Search About

Anonymous Methods As ThreadStarts

Author
10 Mar 2006 1:56 PM
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());
        }
    }
}

Author
10 Mar 2006 2:09 PM
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.

<d225***@yahoo.com> wrote in message
Show quoteHide quote
news:1141998999.464222.179090@i39g2000cwa.googlegroups.com...
>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());
>        }
>    }
> }
>
Are all your drivers up to date? click for free checkup

Author
10 Mar 2006 2:12 PM
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

<d225***@yahoo.com> wrote in message
Show quoteHide quote
news:1141998999.464222.179090@i39g2000cwa.googlegroups.com...
>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());
>        }
>    }
> }
>
Author
10 Mar 2006 2:53 PM
Lasse V=e5gs=e6ther Karlsen
> 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.

--
Lasse Vågsæther Karlsen
http://usinglvkblog.blogspot.com/
mailto:la***@vkarlsen.no
PGP KeyID: 0x2A42A1C2
Author
10 Mar 2006 2:56 PM
Jon Skeet [C# MVP]
d225***@yahoo.com wrote:
> 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
Author
10 Mar 2006 2:57 PM
Willy Denoyette [MVP]
<d225***@yahoo.com> wrote in message
Show quoteHide quote
news:1141998999.464222.179090@i39g2000cwa.googlegroups.com...
|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.
Author
10 Mar 2006 6:08 PM
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);

}


--
William Stacey [MVP]
Author
10 Mar 2006 6:15 PM
d225563
Thanks.  I got what I was looking for.

Bookmark and Share