8

I've been getting into Threads lately while reading the very nice pdf from Rob Miles (here). He had an example on page 160 (2012, C# pdf) but it didn't write to the console just did empty loops.

I wrote a very simple thread producing loop that creates 10 threads that are writing their IDs to the screen on each multiple of 1000. This was nice - it showed me how threads were running together. My Questions begins with why is my output so confused? Often when I run the program below, I get multiple "Thread 3 Finished" lines where I'm pretty certain, I should only have one of each.

I added a "lock" from MSDN to the loop but it still seems to produce odd output (I'll put an example below).

    namespace ConsoleApplication1
    {
        class Program
        {
            static object sync = new object();

            static void Main(string[] args)
            {
                for (int i = 0; i < 10; i++)
                {
                    Thread myThread = new Thread(() => DoThis(i));
                    myThread.Start();
                }
                Console.ReadLine();
            }

            static void DoThis(int s)
            {
                lock (sync) // a new addition that hasn't helped
                {
                    for (long j = 0; j < 10000; j++)
                    {
                        if (j % 1000 == 0) Console.Write(String.Format("{0}.", s));
                    }
                    Console.WriteLine("\r\nThread {0} Finished", s);
                    Debug.Print("\r\nThread {0} Finished", s); //<-- added to debug

                }
            }
        }
    }

I thought I was doing okay - I have local variables (my counting loop), I have an int that is passed presumably not by reference and later I tried to lock it while doing it's loop. No joy.What would I need to do to make the output look sensible? I tried Deubg.Print to troubleshoot but it has errors too (below).

Eventually, I want to use threading in a larger application but if I can't get it right here, I'm not sure I want to!

Example Output from the debug.print line at the end: (note the multiples) ...

Thread 1 Done
The thread '<No Name>' (0x15cc) has exited with code 0 (0x0).

Thread 9 Done
The thread '<No Name>' (0x1d0c) has exited with code 0 (0x0).

Thread 6 Done
The thread '<No Name>' (0x2248) has exited with code 0 (0x0).

Thread 10 Done
The thread '<No Name>' (0x22bc) has exited with code 0 (0x0).

Thread 9 Done
The thread '<No Name>' (0x85c) has exited with code 0 (0x0).

Thread 9 Done
The thread '<No Name>' (0x1628) has exited with code 0 (0x0).

Thread 3 Done
The thread '<No Name>' (0x2384) has exited with code 0 (0x0).

Thread 6 Done

Thread 2 Done

Thread 4 Done
The thread '<No Name>' (0x2348) has exited with code 0 (0x0).
The thread '<No Name>' (0x2420) has exited with code 0 (0x0).
The thread '<No Name>' (0x1ea8) has exited with code 0 (0x0).

Let me know if I can offer any more info on what I've tried.

Sisyphus
  • 679
  • 6
  • 21
  • 1
    I think your variable in the loop is captured. Store i in a temporary variable. But if you mean why the output is not ordered.. the execution of a thread is not guaranteed to be in the order it was started. – Lews Therin May 13 '13 at 10:38
  • not worried about the exact order but it looks as if @MatthewWatson has answered given the same answer ... thankyou. – Sisyphus May 13 '13 at 10:48
  • possible duplicate of [C# Captured Variable In Loop](http://stackoverflow.com/questions/271440/c-sharp-captured-variable-in-loop) – nawfal Nov 02 '13 at 06:37

2 Answers2

10

Your problem here is that you are using a "modified closure" on the loop variable.

Although this has been fixed for foreach loops, for loops still have the issue (and always will)

To fix it, change your Main() to this:

static void Main(string[] args)
{
    for (int i = 0; i < 10; i++)
    {
        int copy = i; // Make a copy of i to prevent the "modified closure" problem.
        Thread myThread = new Thread(() => DoThis(copy));
        myThread.Start();
    }
    Console.ReadLine();
}

See here for more details:

Access to Modified Closure

Eric Lippert's Article on Closures

Community
  • 1
  • 1
Matthew Watson
  • 104,400
  • 10
  • 158
  • 276
  • Okay, I was looking suspiciously at that "i" ... Let me have a quick play. – Sisyphus May 13 '13 at 10:42
  • 2
    @IV4 The closure thing hasn't changed for `for` loops, only for `foreach` loops. See http://stackoverflow.com/questions/16264289/captured-closure-loop-variable-in-c-sharp-5-0 – Matthew Watson May 13 '13 at 10:45
  • @I4V It's the same with .NET 4.5 and C# 5. The loop `for (int i = 0; i < 10; i++) { Thread myThread = new Thread(() => DoThis(i)); myThread.Start(); }` has the variable `i` as an "outer" variable, just like it had been `int i; /* declared before loop */ for (i = 0; i < 10; i++) { Thread myThread = new Thread(() => DoThis(i)); myThread.Start(); }`. – Jeppe Stig Nielsen May 13 '13 at 10:45
  • 1
    ... however, had he used `foreach (var i in Enumerable.Range(0, 10)) { Thread myThread = new Thread(() => DoThis(i)); myThread.Start(); }`, it would have depended on the version of C# he used. – Jeppe Stig Nielsen May 13 '13 at 10:48
  • Thank you all! - would seem I just needed to know what to search for! I can see threads being a very interesting challenge. – Sisyphus May 13 '13 at 10:50
  • @Sisyphus You know the same thing happens if you use just one thread? You can try this: `var actions = new List(); for (int i = 0; i < 10; i++) { actions.Add(() => DoThis(i)); } Console.WriteLine("After for loop! How many 'i' have been captured, ten or one? Invoking all actions sequentially:"); foreach (var a in actions) { a(); }` So this is more a matter of understanding what happens if an anonymous function (like a lambda `=>`) refers a local variable that later changes. – Jeppe Stig Nielsen May 13 '13 at 11:05
5

Try using Parallel.For instead of starting threads yourself in a loop:

Parallel.For(0, 10, DoThis);

If you needed to pass an extra argument (say w) as part of that, then you can do this:

var w = 4;
Parallel.For(0, 10, i => DoThis(i, w));

The other thing to consider of course is that the Console object is exclusive. If you use it from one thread, any other threads that try to use it will be blocked until it's finished with.

Also your lock (sync) is going to stop any two threads from doing their operation at the same time.

Bear in mind that your threads are not guaranteed to execute in any particular order.

PhonicUK
  • 13,486
  • 4
  • 43
  • 62
  • I don't really mind what order - the lock was me trying to figure out what was going wrong! I've never used Parallel.For (and many, many other things) so will have a quick read. – Sisyphus May 13 '13 at 10:41
  • can I still pass a variable to the Dothis method? – Sisyphus May 13 '13 at 10:46
  • 1
    `Parallel.For` passes the index (0..9) as the parameter to `Dothis` - `Dothis` happens to match the delegate type its expecting so what I've given you there should 'just work' – PhonicUK May 13 '13 at 10:51
  • Right you are ... just while I've got you "on the hook", how would you modify this if my method was DoWork(int s, int w){...} ? – Sisyphus May 13 '13 at 10:56
  • Cool, thanks - simple now that I see it. I'd give you another upvote if I could! – Sisyphus May 13 '13 at 11:12