Состояние гонки во время старта нити? - PullRequest
3 голосов
/ 18 марта 2010

Я запускаю следующий код для запуска своих потоков, но они не запускаются так, как задумано. По некоторым причинам некоторые потоки начинаются с одинаковых объектов (а некоторые даже не запускаются). Если я пытаюсь отладить, они запускаются просто отлично (добавлена ​​дополнительная задержка, нажав 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 ]

2 голосов
/ 18 марта 2010

Вы только что столкнулись с лямбда-ошибкой (меня зовут).

Вы предоставляете 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.

2 голосов
/ 18 марта 2010

Начиная с поток действительно не запускает поток. Вместо этого это планирует это для выполнения. То есть в какой-то момент он начнет работать, когда это запланировано. Планирование потоков - сложная тема и деталь реализации ОС, поэтому вашему коду не следует ожидать определенного планирования.

Вы также захватываете переменные в своей лямбде. Пожалуйста, см. в этом посте (есть раздел о захваченных переменных), чтобы узнать о проблемах, связанных с этим.

1 голос
/ 18 марта 2010

Кажется, проблема здесь: () => WorkerThread(tmpItem, i)

Я не привык к Func<>, но, похоже, он работает как анонимный делегат в .NET 2.0. Таким образом, вы можете иметь ссылку на аргументы метода WorkerThread(). Следовательно, их значения извлекаются позже (когда поток действительно выполняется).

В этом случае вы, возможно, уже находитесь на следующей итерации вашего основного потока ...

Попробуйте вместо этого:

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

[EDIT] Другая реализация. Более гибкий, если вам нужно передать новые параметры потоку в будущем.

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();
        });
    }
}
0 голосов
/ 18 марта 2010

Спасибо всем вам!

Я только что реализовал пул потоков, и это сработало как шарм - с дополнительным бонусом - не создавать слишком много потоков одновременно.

Я посмотрю и другие решения, но на этот раз пул потоков избавит меня от необходимости вручную проверять бозо со слишком большим количеством конфигов;)

0 голосов
/ 18 марта 2010

Я думаю, что ошибка в другом месте. Вот несколько советов, которые помогут вам отладить:

  1. Дайте имя, содержащее каждый поток, и отобразите имя потока вместо имени элемента конфигурации:

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

  2. Отображение содержимого config.getAllItems (), возможно, некоторые элементы имеют одинаковое имя (дублировано)

===========

Вот некоторые дополнительные сведения о многопоточности с winforms:

  1. не создавайте новую тему напрямую, используйте вместо этого ThreadPool:

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

  2. Если вы действительно хотите создавать свои потоки, используйте this.BeginInvoke вместо этого. Invoke ваш рабочий поток завершит раньше => менее параллельный поток => лучшая глобальная производительность
  3. не вызывайте Thread.Sleep в цикле, просто сделайте большой сон: Thread.Sleep (10 * mul * 100);

Надеюсь, это вам поможет.

0 голосов
/ 18 марта 2010

Вы не можете предполагать, что потоки будут работать в том же порядке, в котором они были вызваны, если только вы не принудительно вызовете их и не создадите зависимость между ними.

Таким образом, реальный вопрос - какова ваша цель?

0 голосов
/ 18 марта 2010

Вам действительно нужно порождать потоки вручную (что довольно дорого)? Вместо этого вы можете попробовать переключиться на ThreadPool.

...