Threadpools - возможная проблема порядка выполнения потоков - PullRequest
2 голосов
/ 17 декабря 2010

Я изучал, как использовать пулы потоков, но я не уверен, что каждый из потоков в пуле выполняется правильно, и я подозреваю, что некоторые из них выполняются более одного раза. Я сократил код до минимума и использовал Debug.WriteLine, чтобы попытаться выяснить, что происходит, но это дает некоторые странные результаты.

Мой код выглядит следующим образом (на основе кода из ( WaitAll для нескольких дескрипторов в потоке STA не поддерживается ):

public void ThreadCheck()
    {
        string[] files;
        classImport Import;
        CountdownEvent done = new CountdownEvent(1);
        ManualResetEvent[] doneEvents = new ManualResetEvent[10];

        try
        {
            files = Directory.GetFiles(importDirectory, "*.ZIP");

            for (int j = 0; j < doneEvents.Length; j++)
            {
                done.AddCount();
                Import = new classImport(j, files[j], workingDirectory + @"\" + j.ToString(), doneEvents[j]);
                ThreadPool.QueueUserWorkItem(
                (state) =>
                {
                    try
                    {
                        Import.ThreadPoolCallBack(state);
                        Debug.WriteLine("Thread " + j.ToString() + " started");
                    }
                    finally
                    {
                        done.Signal();
                    }
                }, j);

            }

            done.Signal();
            done.Wait();                            
        }
        catch (Exception ex)
        {
            Debug.WriteLine("Error in ThreadCheck():\n" + ex.ToString());
        }
    }

classImport.ThreadPoolCallBack фактически ничего не делает в данный момент.

Если я пошагово выполняю код, я получаю:

Тема 1 запущена Тема 2 началась .... весь путь к .... Тема 10 запущена

Однако, если я запускаю его вручную, окно «Вывод» заполняется «Тема 10 запущена»

У меня вопрос: что-то не так с моим кодом для использования пула потоков или результаты Debug.WriteLine путаются из-за нескольких потоков?

Ответы [ 3 ]

3 голосов
/ 17 декабря 2010

Проблема в том, что вы используете переменную цикла (j) в лямбда-выражении.

Подробности того, почему это проблема, довольно длинны - см. Эрик Липперт в блоге для подробностей (также читайте часть 2 ).

К счастью, это просто: просто создайте новую локальную переменную внутри цикла и используйте ее влямбда-выражение:

for (int j = 0; j < doneEvents.Length; j++)
{
    int localCopyOfJ = j;

    ... use localCopyOfJ within the lambda ...
}

Для остальной части тела цикла можно использовать только j - только когда оно захвачено лямбда-выражением или анонимным методом, оно становится проблемой.

Это распространенная проблема, которая сбивает с толку многих людей - команда C # рассмотрела изменения в поведении для цикла foreach (где действительно выглядит так, как будто вы уже объявляете отдельную переменнуюна каждой итерации), но это может вызвать интересные проблемы совместимости.(Вы можете написать код на C # 5, который работает нормально, а с C # 4 он может хорошо скомпилироваться, но на самом деле не работает, например.)

2 голосов
/ 17 декабря 2010

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

Может выглядеть , как будто каждая задача пула потоков видит свою «версию» j, но это не так. Другими словами, последующих мутаций в j после создания задачи является видимым для задачи.

Когда вы медленно просматриваете свой код, пул потоков выполняет каждую задачу, прежде чем переменная сможет измениться, поэтому вы получаете ожидаемый результат ( одно значение для переменной эффективно ") связано с одна задача). В производстве это не так. Похоже, что для вашего конкретного тестового прогона цикл завершен до того, как какая-либо из задач имела возможность запустить. Вот почему все задачи увидели одинаковое «последнее» значение для j (Учитывая время, которое требуется для планирования задания в пуле потоков, я думаю, что этот вывод будет типичным .) Но это никак не гарантируется; вы можете видеть любой вывод, в зависимости от конкретных временных характеристик среды, в которой вы запускаете этот код.

К счастью, это просто:

for (int j = 0; j < doneEvents.Length; j++)
{
   int jCopy = j;
   // work with jCopy instead of j

Теперь каждая задача будет «владеть» определенным значением переменной цикла.

1 голос
/ 17 декабря 2010

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

...