Как дождаться завершения потоков без блокировки графического интерфейса?

У меня есть массив из 2863 объектов. Я хочу, чтобы в двух «прогонах» на 1000 объектов читались данные массива 4 потоками (количество процессоров под управлением ПК).

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

Single run size (default) = 1000 elements
Number of runs = 2
Extra thread run size = 866 elements

Starting run [1 / 2]
Thread as readDCMTags(i=0,firstIndex=0, lastIndex=249
Thread as readDCMTags(i=1,firstIndex=250, lastIndex=499
Thread as readDCMTags(i=2,firstIndex=500, lastIndex=749
Thread as readDCMTags(i=3,firstIndex=750, lastIndex=999

Starting run [2 / 2]
Thread as readDCMTags(i=0,firstIndex=1000, lastIndex=1249
Thread as readDCMTags(i=1,firstIndex=1250, lastIndex=1499
Thread as readDCMTags(i=2,firstIndex=1500, lastIndex=1749
Thread as readDCMTags(i=3,firstIndex=1750, lastIndex=1999
Extra Thread as readDCMTags(i=1,firstIndex=2000, lastIndex=2865

Однако текущий исходный код запускает все потоки сразу, он не ждет RUN TO END. Когда я присоединяюсь к потокам из текущего запуска, графический интерфейс зависает. Как решить проблему?

Исходный код:

nrOfChunks = 2866 / 1000;
int leftOverChunk = 2866  % 1000;

for(int z = 0; z < nrOfChunks; z++)
{
    addToStatusPanel("\nStarting run [" + (z+1).ToString() + " / " + nrOfChunks.ToString() + "]");

    int indexesPerThread = 1000 / 5; #nrOfThreads
    int leftOverIndexes = 1000 % 5; #nrOfThreads

    threads = new Thread[nrOfThreads];
    threadProgress = new int[nrOfThreads];
    threadDCMRead = new int[nrOfThreads];

    for(int i = 0; i < nrOfThreads; i++)
    {
        int firstIndex = (i * indexesPerThread+z*Convert.ToInt32(chunkSizeTextBox.Text));
        int lastIndex = firstIndex + indexesPerThread - 1;

        if(i == (nrOfThreads- 1))
        {
            if(i == (nrOfThreads - 1))
            {
                lastIndex += leftOverIndexes;
            }
        }

        addToStatusPanel("readDCMTags(i=" + i.ToString() + ",firstIndex=" + firstIndex.ToString() + ", lastIndex=" + lastIndex.ToString());

        threads[i] = new Thread(() => readDCMTags(i.ToString(), firstIndex, lastIndex));
        threads[i].Name = i.ToString();

       threads[i].Start();
    }

    if(z == (nrOfChunks - 1))
    {
        int firstIndex = (nrOfChunks * Convert.ToInt32(chunkSizeTextBox.Text));
        int lastIndex = firstIndex + leftOverChunk - 1;

        addToStatusPanel("readDCMTags(i=" + z.ToString() + ",firstIndex=" + firstIndex.ToString() + ", lastIndex=" + lastIndex.ToString());
    }
}

Добавление после цикла for(int i = 0; i < nrOfThreads; i++) команды соединения для массива потоков перед переходом к следующему циклу выполнения for(int z = 0; z < nrOfChunks; z++) приводит к зависанию графического интерфейса.


person ta007    schedule 17.10.2013    source источник
comment
Не ждите в обработчиках событий завершения потоков (или чего-либо еще). Прекратите использовать функцию Join(). Вызвать/начать вызов.   -  person Martin James    schedule 17.10.2013
comment
Иными словами, если вы вызовете блокирующее ожидание внутри процедуры действия конечного автомата, она прекратит обработку входных данных.   -  person Martin James    schedule 17.10.2013
comment
Поместите весь код выше в BackgroundWorker и прослушайте событие RunWorkerCompleted. (но используйте TPL или PLINQ вместо потоков. Это намного проще)   -  person adrianm    schedule 17.10.2013


Ответы (2)


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

Вместо этого вы хотите, чтобы что-то произошло, когда все потоки будут завершены. То, что "все потоки завершены" - это событие. Таким образом, лучшим вариантом будет ожидание в фоновом потоке и запуск события после завершения всех потоков.

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

Изменить: псевдокод (не проверено, просто идея).

waitBg = new Thread(() => 
  {
    foreach (thread in threads)
      thread.WaitFor();

     // All threads have finished
     if (allThreadFinishedEvent != null)
        allThreadFinishedEvent();
  }
);

Затем в обработчике allThreadFinishedEvent вы делаете все, что хотите (не забудьте отправить его в основной поток, если хотите что-то изменить в пользовательском интерфейсе, так как это будет выполнено в контексте потока bg).

person Jorge Córdoba    schedule 17.10.2013
comment
Итак, я должен установить каждый поток в качестве фона во внутреннем цикле, а затем как остановить основной цикл? Как удерживать основной цикл до тех пор, пока не произойдет событие? - person ta007; 17.10.2013

Как насчет того, чтобы поместить эту логику потоков в фоновый рабочий процесс и отправить результат вашего фонового рабочего процесса обратно в ваш интерфейс. Таким образом, ваш интерфейс не будет заблокирован, пока программа обрабатывает потоки.

вы можете найти пример msdn для инициализации и использования backgroundworker здесь.

Я думаю, что это должен быть правильный путь вперед.

person XikiryoX    schedule 17.10.2013
comment
Мне нужно будет создать массив фоновых рабочих, какой метод или команда будет препятствовать продолжению основного цикла? - person ta007; 17.10.2013
comment
Не совсем уверен, понимаю ли я, что вы имеете в виду, но в основном то, как я решал проблемы с зависанием графического интерфейса в прошлом, - это использование основного метода для интерфейса, и все события, которые он делает, просто обрабатываются вашим основным потоком. Ваш основной поток (или событие, в зависимости от ваших потребностей) запустит вашего фонового рабочего через backgroundworker_DoWork, и вы сможете зафиксировать прогресс через WorkerSupportsProgress. Внутри этого фонового рабочего процесса, который будет работать в потоке, отличном от вашего основного, вы можете поместить свой код. Ваш графический интерфейс использует другой поток и не будет затронут, и он может отображать прогресс - person XikiryoX; 17.10.2013