Обновление индикатора выполнения в параллельном цикле - PullRequest
0 голосов
/ 05 мая 2020

У меня есть приложение WinForm, и я пытаюсь обновить индикатор выполнения параллельно l oop. Вот фрагмент моего кода:

Parallel.ForEach(files, (file, state) =>
        {
           //Intialization of parameters

            //do cpu-intensive task
            DoWork();

            UpdateProgress();
        });



 int counter = 0;
 private object updateLock = new object();

void UpdateProgress()
    {
        lock (updateLock)
        {
            counter++;


            if (progressBar1.InvokeRequired)
            {

                progressBar1.Invoke(() => { progressBar1.SetProgress(counter); });
            }
            else
            {
                progressBar1.SetProgress(counter);
            }


        }
    }

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

 public static void SetProgress(this ProgressBar bar, int value)
    {
        if (value == bar.Maximum)
        {

            bar.Maximum = value + 1;
            bar.Value = value + 1;
            bar.Maximum = value;
        }
        else
        {
            bar.Value = value + 1;
        }

        bar.Value = value;
    }

Весь процесс, кажется, работает нормально, но У меня проблема с обновлением индикатора выполнения. Случайно я вижу, что анимация выполнения перемещается вперед и назад, например, от go до 33/150, затем до 31/150 и затем до 32/150. Хотя я использовал объект блокировки синхронизации для соответствующего обновления хода выполнения на каждом шаге, кажется, что сообщения в основном потоке пользовательского интерфейса обрабатываются не по порядку или что-то не так с кодом.

Есть идеи, в чем может быть проблема?

Заранее спасибо.

[ОБНОВЛЕНИЕ]

enter image description here

1 Ответ

0 голосов
/ 06 мая 2020

Проблема связана с тем, как работает Parallel.ForEach. Вы можете подумать, что он использует только фоновые потоки для выполнения работы, но на самом деле он также использует текущий поток. Другими словами, во время выполнения Parallel.ForEach текущий поток играет роль рабочего потока. В вашем случае текущий поток - это поток пользовательского интерфейса. Условие if (progressBar1.InvokeRequired) оценивается как true для фоновых потоков, участвующих в операции, и false для потока пользовательского интерфейса.

Фоновые потоки вызывают метод progressBar1.Invoke в вашем примере. В отличие от BeginInvoke, Invoke является методом блокировки и возвращается только после того, как поток пользовательского интерфейса обработает предоставленный делегат. Поскольку поток пользовательского интерфейса занят обработкой своего собственного раздела коллекции files, поток Invoke будет заблокирован, поэтому все фоновые потоки застрянут, и единственный поток, который продолжит работу, будет потоком пользовательского интерфейса. В конце поток пользовательского интерфейса должен будет дождаться, пока другие потоки доставят результат единственного файла, который они получили изначально для обработки, что они не смогут сделать, поэтому Parallel.ForEach зайдет в тупик. По крайней мере, это ожидаемый результат опубликованного вами кода. Поскольку вы не наблюдаете тупик, я предполагаю, что в вашем примере отсутствует какая-то строка кода (возможно, вызов Application.DoEvents?), Которая разрешает ситуацию тупика.

Самый простой способ исправить это Неприятная ситуация заключается в том, что пользовательский интерфейс не может стать рабочим потоком. Просто используйте метод Task.Run, чтобы выгрузить всю параллельную обработку в поток ThreadPool:

await Task.Run(() =>
{
    Parallel.ForEach(//...
});

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

После применения этого исправления вы можете сделать свой код более элегантным, удалив все эти уродливые InvokeRequired / Invoke и заменив его современным Progress объектом. Это также тривиально упростило бы отделение logi c обработки файлов от logi c, относящегося к пользовательскому интерфейсу, если вы сочтете это желательным с архитектурной точки зрения. Вы можете прочитать эту статью, если хотите узнать, как использовать класс Progress.

...