none
Как из Parallel.ForEach управлять progressBar? RRS feed

  • Вопрос

  • int blok_count = количество_кубиков_в_файле();
                List<int> List_i_blok = new List<int>();
                for (int i_blok = 0; i_blok < blok_count; i_blok++)
                {
                    List_i_blok.Add(i_blok);
                }

    Не совсем изящно, но Вы уже извините, только начал изучать Parallel.ForEach.

    Parallel.ForEach(List_i_blok, i_blok =>
                            {
                                List<int[,]> Key_i_blok = ключ_для_кубика();
    
                                multiply_flag = true; //шифрование_MKey
                                MyCrypt.crypt_MatrixMultiply_in(ref listFile_MKey, Key_i_blok, multiply_flag, i_blok);
                                //progressBar_blok_MKey.Value = i_blok + 1;
                                //SetProgressBarValue(progressBar_blok_MKey, i_blok + 1);
    
                                multiply_flag = false; //шифрование_KeyM
                                MyCrypt.crypt_MatrixMultiply_in(ref listFile_KeyM, Key_i_blok, multiply_flag, i_blok);
                                //progressBar_blok_KeyM.Value = i_blok + 1;
                                //SetProgressBarValue(progressBar_blok_KeyM, i_blok + 1);
                            }
                            );
    private void SetProgressBarValue(ProgressBar prb, int value)
            {
                if (this.InvokeRequired)
                {
                    this.Invoke((Action<ProgressBar, int>)SetProgressBarValue, prb, value);
                }
                else
                {
                    prb.Value = value;
                }
            }

    Команда progressBar_blok_MKey.Value = i_blok + 1; не работает.

    Выдает ошибку: «Недопустимая операция в нескольких потоках: попытка доступа к элементу управления 'progressBar_blok_MKey' не из того потока, в котором он был создан

    Если пользуюсь командой SetProgressBarValue(progressBar_blok_MKey, i_blok + 1); то оба progressBar заполняются где-то до середины, после чего не двигаются, а цикл Parallel.ForEach не заканчивается (бесконечно крутится в себе).

    Как можно управлять progressBar из Parallel.ForEach?



    26 июня 2013 г. 14:01

Все ответы

  • Неужели это такой сложный вопрос?

    27 июня 2013 г. 14:55
  • Добрый день.

    Да нет, вопрос не сложный, просто как из потока, отличного от потока в котором создавались элементы управления, получить доступ к элементам управления, вам уже подсказали. Почему ваш код зависает? Из приведенных фрагментов не совсем понятно. Вот вам и не могут ничего подсказать. Попробуйте пройти в отладчике, посмотрите значения ваших переменных i_block. Посмотрите, не уходят ли в вечный цикл метод crypt_MatrixMultiply_in. 

    28 июня 2013 г. 4:38
    Отвечающий
  • Может быть проблема в том, что Parallel.ForEach обращается к статическому классу? И из-за этого уходит в вечный цикл.


    Можете посоветовать, как можно отлаживать Parallel.ForEach?

    А то, когда уходит на вечный цикл, то нажимаю «остановить», а внутрь Parallel.ForEach зайти не могу. Хотя отладка показывает что Parallel.ForEach выполняется.


    Если строка

    //SetProgressBarValue(progressBar_blok_MKey, i_blok + 1);

    закомментирована, то все работает.

    Если снять комментарий, то уходит на вечный цикл.

    Полагаю, что проблема именно в SetProgressBarValue.

    • Изменено sg6336 28 июня 2013 г. 7:18 изменил
    28 июня 2013 г. 7:07
  • Возможно, у Вас получается взаимоблокировка нескольких потоков в SetProgressBarValue. Ведь Invoke - метод синхронный. Попробуйте BeginInvoke.
    22 сентября 2013 г. 19:52
  • Верно подмечено, что проблема во взаимодействии потоков. Однако, вряд ли происходит взаимоблокировка.

    Метод Parallel.ForEach разбивает выполнение на несколько частей, и выполняться они будут в произвольном порядке. То есть, например, в коллекции 100 элементов, и обрабатываться могут сначала первые десять, потом с 50-го по 80-й, потом с 11 по 35, затем с 81 по 100, потом оставшиеся. И вот какой последним был выполнен, номер того и присвоится ProgressBar'у.

    В данной конкретной ситуации можно просто воспользоваться методом ProgressBar.PerformStep(), вызывая его на каждой итерации (или ProgressBar.Increment(1)).

     

    sg6336:
    > цикл Parallel.ForEach не заканчивается (бесконечно крутится в себе)

    Полагаю, цикл всё-таки заканчивался, просто прогрессбар показывал последнее обработанное значение.

    22 сентября 2013 г. 20:47
  • Уделил ещё некоторое время поискам точной информации о потокобезопасности.

    Известно, что члены экземпляра ProgressBar не потокобезопасны. Следовательно, любое изменение свойства Value (хоть прямым присвоением, хоть через вызов метода) из нескольких потоков может привести к состоянию гонки. Следовательно, необходимо использовать блокировку (что в итоге ухудшит производительность, увы).

    22 сентября 2013 г. 21:26
  • Не путайте кислое с холодным. Все обращения к ProgressBar идут через один и тот же поток. А именно из потока, в котором была создана ваша форма. Это достаточно легко понять, посмотрев вот на этот код:

    this.Invoke((Action<ProgressBar, int>)SetProgressBarValue, prb, value);

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

    У вас уход в вечный цикл или ProgressBar до конца не заполняется?

    23 сентября 2013 г. 3:43
    Отвечающий
  • Все обращения к ProgressBar идут через один и тот же поток. А именно из потока, в котором была создана ваша форма.


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

    Верно, я был не прав.
    23 сентября 2013 г. 19:37
  • Надеюсь, теперь я дам правильный ответ :).

    Методы класса Parallel используют для распараллеливания класс System.Threading.Tasks.Task. Важно то, что этот Task не напрямую соответствует потоку System.Threading.Thread. Несколько тасков могут одновременно исполняться в одном управляемом потоке. В итоге именно это приводит к проблеме в данном случае.

    Итоговое решение - нужно использовать контекст синхронизации:

    var options = new ParallelOptions();
    options.TaskScheduler = TaskScheduler.FromCurrentSynchronizationContext();
    
    Parallel.ForEach(List_i_blok, options, i_blok =>
    {
    
    });

    Что любопытно, при этом даже прямое присваивание prb.Value = value; работает корректно. То есть синхронизация заставляет вести обработку элементов в изначальном порядке. Во всяком случае, у меня тестовый проект работает корректно.

    Надеюсь, топикстартер sg6336 появится в теме и напишет, как он решил проблему.

    -----

    Вспомнил про другой вопрос на похожую тему: Доступ к textBox из другого потока (используя Parallel.Invoke)?

    И там выходом из проблемы является применение контекста синхронизации.

    • Изменено Petalvik 23 сентября 2013 г. 20:11
    23 сентября 2013 г. 19:47