2010-03-18 6 views
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(); 
    }); 
} 

Кто-нибудь может мне помочь?

+0

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

+1

В многопоточном приложении ... отладка не поможет ... это даст вам отличный результат, чем в режиме реального времени ... Используйте заявление журнала/печати ... –

ответ

2

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

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

+0

Нет, это дано, но я действительно ожидал быть в состоянии отправить объект и иметь правильный объект, доступный в потоке ...? ... – user296353

0

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

+0

Хммм ... Я посмотрю на него :) Спасибо! – user296353

0

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

Итак, настоящий вопрос - какова ваша цель?

+0

Спасибо за быстрый ответ, все! Это приложение должно сохранять несколько конфигураций для пользователя, а затем запускать некоторые приложения для создания музыкальных автоматов для мультимедийных устройств. В основном я хочу создать один поток для каждой комбинации конфигураций (для создания музыкальных автоматов требуется несколько программ), где configItem содержит необходимую информацию для каждого потока. Это результат, который я получаю от своего теста: Тема 2 Завершить! Тема 2 Завершить! Тема 4 Завершить! Темная резьба завершена! Проверка резьбы завершена! Проверка резьбы завершена! --- Как вы можете видеть, некоторые потоки начинаются дважды – user296353

0

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

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

    this.textBox1.Text + = «Нить» + Thread.Current.Name + "Complete! \ R \ n";

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

===========

Здесь некоторые дополнительные сведения о многопоточной с WinForms:

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

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

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

Я надеюсь, что это вам поможет.

2

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

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

Я согласен с этой диагностикой ;-) Но вы должны применить одно и то же исправление к переменной «i».Посмотрите на мой ответ и подумайте над определением класса ThreadStartData или чего-то подобного – Seb

+2

См. «Закрытие переменной цикла, считающейся вредной»: http://blogs.msdn.com/ericlippert/archive/2009/11/12/ close-over-the-loop-variable-Consider-harm.aspx – LukeH

+0

@Seb: Вы правы. Я просто не видел «я». Обновлен мой ответ и для этой переменной. – Oliver

1

Проблема, кажется, есть: () => 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

Если вы используете анонимный класс в качестве аргумента, как вы пишете подпись 'WorkerThread'? – Oliver

+0

делегат ParametrizedThreadStart имеет объект как уникальный аргумент, извините. Но вы можете определить класс, содержащий данные потока, и метод Start(). Я приведу еще один пример кода. – Seb

+0

@ Seb: Пока все хорошо, но вы столкнулись с одной и той же проблемой, потому что в вашем цикле 'foreach' вы не использовали копию' i' и 'tmpItem'. – Oliver

0

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

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

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

+1

, если вы нашли один (или более) ответ (-ы) полезен, вы должны их перенести. И последнее, но не менее важное: вы должны пометить ответ (который вам больше всего помог) как правильный. Вот как работает SO. – Oliver

Смежные вопросы