views:

116

answers:

7

Hi,

I'm running the following code to start my threads, but they don't start as intended. For some reason, some of the threads start with the same objects (and some don't even start). If I try to debug, they start just fine (extra delay added by me clicking F10 to step through the code).

These are the functions in my forms app:

private void startWorkerThreads()
{
    int numThreads = config.getAllItems().Count;
    int i = 0;

    foreach (ConfigurationItem tmpItem in config.getAllItems())
    {
        i++;
        var t = new Thread(() => WorkerThread(tmpItem, i));
        t.Start();
        //return t;
    }
}

private void WorkerThread(ConfigurationItem cfgItem, int mul) 
{
    for (int i = 0; i < 100; i++)
    {
        Thread.Sleep(10*mul);
    }
    this.Invoke((ThreadStart)delegate()
    {
        this.textBox1.Text += "Thread " + cfgItem.name + " Complete!\r\n";
        this.textBox1.SelectionStart = textBox1.Text.Length;
        this.textBox1.ScrollToCaret();
    });
}

Anyone able to help me out?

Cheers!

+2  A: 

Starting a thread doesn't really start the thread. Instead it schedules it for execution. I.e. at some point it will get to run when it is scheduled. Scheduling threads is a complex topic and an implementation detail of the OS, so your code should not expect a certain scheduling.

You're also capturing variables in your lambda. Please see this post (there is a section on Captured Variables) for the problems associated with doing that.

Brian Rasmussen
No, that's a given, but I did expect to be able to send an object and have the correct object available in the thread...?...
A: 

Do you really need to spawn threads manually (which is a rather expensive task) ? You could try to switch to the ThreadPool instead.

Sylvestre Equy
Hmmm...I'll have a look at it :) Thanks!
A: 

You can't assume that the threads will run in the same order they were called, unless you force it, and cause a dependency between them.

So the real question is - what is your goal ?

Dani
Thanks for the quick reply, all!This application is supposed to save several configs for a user, and then run some apps to generate jukeboxes for multimedia devices. What I basically want to do is start one thread for each combination of configs (there are several programs required to generate the jukeboxes), where a configItem holds the necessary info for each thread.This is the output I'm getting from my test:Thread 2 Complete!Thread 2 Complete!Thread 4 Complete!Thread temp Complete!Thread test Complete!Thread test Complete!---As you can see, some threads start twice
A: 

Hello,

I think that the error is somewhere else. Here are some hints to help you debug :

  1. Give a name containing to each thread, and display the thread name instead of the config item name :

    this.textBox1.Text += "Thread " + Thread.Current.Name + " Complete!\r\n";

  2. Display the content of config.getAllItems(), may be that some items has the same name (duplicated)

===========

Here are some additional information about multi threading with winforms:

  1. dont create new Thread directly, use the ThreadPool instead :

    ThreadPool.QueueUserWorkItem(state => { WorkerThread(tmpItem, i); });

  2. If you really want to creat your threads, use this.BeginInvoke instead of this.Invoke your worker thread will finish sooner => less concurrent thread => better global performance
  3. don't call Thread.Sleep in a loop, just do a big sleep: Thread.Sleep(10*mul*100);

I hope that this will help you.

Manitra Andriamitondra
+2  A: 

You just run into the (be me called) lambda error.

You provide the ConfigurationItem from the foreach loop directly. This leads to the fact, that all your threads get the same item (the last one).

To get this to work you have to create a reference for each item and apply this to each thread:

foreach (ConfigurationItem tmpItem in config.getAllItems())
{
        i++;
        var currentI = i;
        var currentItem = tmpItem;
        var t = new Thread(() => WorkerThread(currentItem, currentI));
        t.Start();
        //return t;
}

And you should also consider using a ThreadPool.

Oliver
I agree with this diagnostic ;-) But you have to apply the same fix to the 'i' variable. Have a look at my answer and consider defining a 'ThreadStartData' class or something like that
Seb
See "Closing over the loop variable considered harmful": http://blogs.msdn.com/ericlippert/archive/2009/11/12/closing-over-the-loop-variable-considered-harmful.aspx
LukeH
@Seb: You're right. I just didn't see the `i`. Updated my answer also for this variable.
Oliver
@user296353: Instead of this solution you should better refactor it to the code proposed by Seb.
Oliver
+1  A: 

The problem seems to be there : () => WorkerThread(tmpItem, i)

I'm not used to Func<> but it seems to work like anonymous delegates in .NET 2.0. Thus, you may have a reference to the arguments of the WorkerThread() method. Hence, their values are retrieved later (when the thread actually runs).

In this case, you may already be at the next iteration of your main thread...

Try this instead :

var t = new Thread(new ParametrizedThreadStart(WorkerThread));
t.Start(new { ConfigurationItem = tmpItem, Index = i } );

[EDIT] Other implementation. More flexible if you need to pass new parameters to the thread in the future.

private void startWorkerThreads()
{
    int numThreads = config.getAllItems().Count;
    int i = 0;

    foreach (ConfigurationItem tmpItem in config.getAllItems())
    {
            i++;
            var wt = new WorkerThread(tmpItem, i);
            wt.Start();
            //return t;
    }
}
private class WorkerThread
{
    private ConfigurationItem _cfgItem;
    private int _mul;
    private Thread _thread;
    public WorkerThread(ConfigurationItem cfgItem, int mul) {
        _cfgItem = cfgItem;
        _mul = mul;
    }
    public void Start()
    {
        _thread = new Thread(Run);
        _thread.Start();
    }
    private void Run()
    {
        for (int i = 0; i < 100; i++)
        {
            Thread.Sleep(10 * _mul);
        }
        this.Invoke((ThreadStart)delegate()
        {
            this.textBox1.Text += "Thread " + _cfgItem.name + " Complete!\r\n";
            this.textBox1.SelectionStart = textBox1.Text.Length;
            this.textBox1.ScrollToCaret();
        });
    }
}
Seb
If you use an anonymous class as argument, how do you write the signature of `WorkerThread`?
Oliver
a ParametrizedThreadStart delegate has an object as unique argument, sorry. But you may define a class containing the thread data and a Start() method. I'll provide another code sample.
Seb
@Seb: So far so good, but you ran into the same problem, cause in your `foreach` loop you didn't use a copy of `i` and `tmpItem`.
Oliver
@Oliver : no because by avoiding the use of Func<> and anonymous method, all parameters are correctly initialized in the WorkerThread class. The reference problem in the initial case is fully due to the implementation of anonymous methods and Func<> : the generated MSIL contains reference to the loop variables instad of copying the values and pointers.
Seb
@Oliver: You should have a look at the paragraph #4 http://www.yoda.arachsys.com/csharp/teasers.html , it is a good explanation :-)
Seb
@Seb: Yes, you're right. I'm also aware about how to prevent such situations (or to leverage them). But i just didn't read carefully enough your code before i posted my comment.
Oliver
A: 

Thanks to all of you!

I just implemented the threadpool, and that worked like a charm - with the added bonus of not spawning too many threads at once.

I'll have a look at the other solutions, too, but this time around the threadpool will save me from having to manually check for bozos with too many configs ;)

if you found one (or more) answer(s) useful, you should upvote them. Last but not least you should mark on answer (which helped you most) as correct. That's how SO works.
Oliver