3

我正在运行以下代码来启动我的线程,但它们没有按预期启动。出于某种原因,一些线程以相同的对象开始(有些甚至不开始)。如果我尝试调试,它们开始就好了(我单击 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();
    });
}

任何人都可以帮助我吗?

4

7 回答 7

2

启动线程并不会真正启动线程。相反,它安排它执行。即在某些时候它会在预定时运行。调度线程是一个复杂的话题和操作系统的实现细节,所以你的代码不应该期望一定的调度。

您还在 lambda 中捕获变量。请参阅这篇文章(有一个关于捕获变量的部分)以了解与这样做相关的问题。

于 2010-03-18T09:29:46.067 回答
2

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

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

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

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。

于 2010-03-18T09:50:31.250 回答
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();
        });
    }
}
于 2010-03-18T09:59:46.600 回答
0

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

于 2010-03-18T09:29:50.157 回答
0

你不能假设线程会按照它们被调用的顺序运行,除非你强制它,并导致它们之间存在依赖关系。

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

于 2010-03-18T09:31:11.977 回答
0

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

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

    this.textBox1.Text += "线程 " + Thread.Current.Name + " 完成!\r\n";

  2. 显示config.getAllItems()的内容,可能是某些item同名(重复)

===========

以下是有关使用 winform 进行多线程处理的一些附加信息:

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

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

  2. 如果您真的想创建线程,请使用 this.BeginInvoke 而不是 this.Invoke 您的工作线程将更快完成 => 更少的并发线程 => 更好的全局性能
  3. 不要在循环中调用 Thread.Sleep,只是做一个大睡眠: Thread.Sleep(10*mul*100);

我希望这会对你有所帮助。

于 2010-03-18T09:49:10.457 回答
0

感谢大家!

我刚刚实现了线程池,这就像一个魅力 - 额外的好处是不会一次产生太多线程。

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

于 2010-03-18T10:41:48.600 回答