线程启动期间的竞争条件?

时间:2010-03-18 09:26:29

标签: c# windows multithreading forms race-condition

我正在运行以下代码来启动我的线程,但它们没有按预期启动。出于某种原因,一些线程以相同的对象开始(有些甚至没有启动)。如果我尝试调试,它们会很好地开始(我点击F10来逐步执行代码时添加了额外的延迟)。

这些是我的表单应用程序中的函数:

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();
    });
}

任何人都可以帮助我吗?

7 个答案:

答案 0 :(得分:2)

Starting线程并没有真正启动线程。相反,它会将其安排执行。即在某些时候,它会在安排时运行。调度线程是一个复杂的主题和操作系统的实现细节,因此您的代码不应该期望某个调度。

你也在你的lambda中捕获变量。有关与此相关的问题,请参阅this post(有一个关于捕获变量的部分)。

答案 1 :(得分:2)

你只是遇到了(被我称之为)lambda错误。

您直接从foreach循环中提供ConfigurationItem。这导致了所有线程获得相同项目(最后一个)的事实。

要使其工作,您必须为每个项目创建一个引用并将其应用于每个线程:

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

你还应该考虑使用ThreadPool。

答案 2 :(得分:1)

问题似乎在那里:() => WorkerThread(tmpItem, i)

我不习惯Func<>,但它似乎像.NET 2.0中的匿名代理一样工作。因此,您可能会引用WorkerThread()方法的参数。因此,它们的值将在以后检索(当线程实际运行时)。

在这种情况下,您可能已经处于主线程的下一次迭代......

请改为尝试:

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

[编辑]其他实施。如果您将来需要将新参数传递给线程,则更灵活。

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();
        });
    }
}

答案 3 :(得分:0)

你真的需要手动生成线程(这是一项相当昂贵的任务)吗?您可以尝试切换到ThreadPool。

答案 4 :(得分:0)

你不能假设线程将以它们被调用的相同顺序运行,除非你强制它,并导致它们之间的依赖。

所以真正的问题是 - 你的目标是什么?

答案 5 :(得分:0)

我认为错误在其他地方。以下是一些可以帮助您调试的提示:

  1. 为每个线程指定一个名称,并显示线程名称而不是配置项名称:

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

  2. 显示config.getAllItems()的内容,可能是某些项目具有相同的名称(重复)

  3. ===========

    以下是有关使用winforms进行多线程处理的一些其他信息:

    1. 不要直接创建新的Thread,而是使用ThreadPool:

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

    2. 如果你真的想要创建你的线程,请使用this.BeginInvoke而不是this.Invoke你的工作线程将很快完成=&gt;较少的并发线程=&gt;更好的全球业绩
    3. 不要在循环中调用Thread.Sleep,只需要大睡一会:Thread.Sleep(10 * mul * 100);
    4. 我希望这会对你有所帮助。

答案 6 :(得分:0)

感谢大家!

我刚刚实现了线程池,它就像一个魅力 - 还有额外的好处,就是不会同时产生过多的线程。

我也会看看其他解决方案,但这次绕线程池将使我不必手动检查有太多配置的bozos;)