Как разблокировать основной поток для пользовательского интерфейса в winforms (используя Parallel.Foreach)?

с использованием С#, .Net Framework 4.5, VS 2012

Попробуйте использовать Parallel.Foreach

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

private void ProcessFileParallel()
    {
        string[] files =
            Directory.GetFiles(@"C:\Users\Public\Pictures\Sample Pictures",
            "*.jpg", SearchOption.AllDirectories); //get source folder
        string dirNew = @"C:\modifiedImages"; //new folder
        Directory.CreateDirectory(dirNew); //create dir
        //usage of parallel and lambda
        Parallel.ForEach(files, currfiles =>
        {
            string fileName = Path.GetFileName(currfiles); //get cur name of file
             //GC for Bitmap
             //create new object of Bitmap
             using (Bitmap bitmap = new Bitmap(currfiles))
            {
                bitmap.RotateFlip(RotateFlipType.Rotate180FlipX); //rotating
                bitmap.Save(Path.Combine(dirNew, fileName)); //save as
                 //anonym delegate - used for safety access to UI elements from secondary thread
                this.Invoke((Action)delegate
                {
                    //caption name change for form
                    this.Text = 
                        string.Format("Curr Thread {0}", 
                        Thread.CurrentThread.ManagedThreadId);
                }
                );
            }
        }
        );            
    }

это работает, но после завершения (когда все изображения повернуты и сохранены в новом месте, а пользовательский интерфейс получил сверху что-то вроде Curr Thread 11) основной поток заблокирован - означает, что пользовательский интерфейс не активен - ничего не может сделать. Вопрос. Как я могу разблокировать элементы пользовательского интерфейса?


person hbk    schedule 23.11.2013    source источник


Ответы (2)


Я начал писать комментарий, но он быстро стал слишком длинным.

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

Parallel.ForEach блокирует поток, в котором он вызывается (который в вашем случае является потоком пользовательского интерфейса), пока не будут завершены все параллельные операции. В то же время у вас есть другие потоки, пытающиеся синхронно вызывать действия в потоке пользовательского интерфейса (который уже заблокирован). Эти вызовы должны ждать разблокировки потока пользовательского интерфейса, но они не разблокируются до тех пор, пока все потоки, не связанные с пользовательским интерфейсом, не закончат свою работу (чего они не могут, потому что ждут). Мат. У вас взаимоблокировка, и ваш вызов Parallel.ForEach никогда не завершится.

На самом деле вы могли бы решить свою проблему, просто заменив this.Invoke на this.BeginInvoke, что асинхронно отправляет работу в поток пользовательского интерфейса и, таким образом, позволяет потокам, не относящимся к пользовательскому интерфейсу, продолжать работу и в конечном итоге завершаться; однако я утверждаю, что разгрузка фактического вызова Parallel.ForEach в пул потоков через Task (как предложил ChrisK) является лучшим решением во всех отношениях.

P.S. Кстати, ваш вызов Thread.CurrentThread.ManagedThreadId всегда вызывается в потоке пользовательского интерфейса и, следовательно, всегда будет возвращать идентификатор вашего потока пользовательского интерфейса, который может быть или не быть потоком, выполняющим работу с изображением. . Если вы хотите знать, какой фактический поток выполняет работу, вам придется сохранить возвращаемое значение из Thread.CurrentThread.ManagedThreadId вне определения вашего делегата, а затем закрыть его.

person Kirill Shlenskiy    schedule 23.11.2013
comment
-У вас тупик. - Ага!!! Спасибо за понятное объяснение! потому что вместо этого просто используйте Task - не полный ответ, который действительно может помочь - person hbk; 24.11.2013
comment
Просто пометка: на мой взгляд, операции ввода-вывода не могут работать параллельно. Потому что механические диски могут читать только по одному адресу за раз. Я бы сначала прочитал каждый файл за файлом, может быть, ряд файлов. Потом делай флипы по сохраненным в ОЗУ файлам в тредах. После этого закрывайте файл за файлом. - person Dennis Ziolkowski; 24.11.2013
comment
но как я понимаю параллельная многопоточность - не параллельная работа - просто манекен - на самом деле если у вас на ПК 1 процесс - параллельная поточность не может быть создана - просто быстрое переключение между процессами - это создает иллюзию быстрой работы для пользователей, но если вы иметь 2 или более процессора - параллельный поток имеет реальный смысл - person hbk; 24.11.2013
comment
@DennisZiolkowski, согласен: когда дело доходит до распараллеливания ввода-вывода, может быть немного скользкий путь. Лично я бы посмотрел на конвейеры, чтобы распараллелить части работы, которые хорошо подходят для этого, и сохранить IO красивым и последовательным. - person Kirill Shlenskiy; 24.11.2013
comment
Я сделал тестовый пример. У меня есть 1000 файлов с одинаковым содержимым, созданным для каждого кода. Затем я использовал Parallel.ForEach и обычный последовательный foreach для перебора имен файлов, создания потока и добавления Hello.. С помощью класса StopWatch я проверил тики, которые он взял. Во-первых, я сделал с Parallel.ForEach, что заняло avg. 4880 тиков. С последовательным foreach это заняло avg. 2480 тиков. Я думаю, что это поведение связано с тем, что поток блокируется, в то время как устройство ввода-вывода фактически читает, пишет, открывает или закрывает. Так что лучше используйте последовательный ввод-вывод :-) - person Dennis Ziolkowski; 24.11.2013
comment
@ Кирилл, не все операции подходят для распараллеливания. При работе с ресурсами, требующими последовательного доступа (как в случае с большим количеством ресурсов ввода-вывода, как указал Деннис), повышенная конкуренция за ресурсы, вызванная параллельным доступом, может привести к снижению производительности. Распараллеливание — сложная, но чрезвычайно интересная тема, и если вы хотите по-настоящему разобраться в этом, я всегда рекомендую бесплатную книгу, в которой рассматриваются многие из этих предостережений: Patterns of Parallel Programming (microsoft.com/en-US/download/details.aspx?id=19222) - person Kirill Shlenskiy; 24.11.2013
comment
@DennisZiolkowski, это хороший показатель, +1. Если у вас есть время, вы можете написать параллельное решение, учитывающее узкое место ввода-вывода. Лично я бы использовал поток данных TPL или даже конвейер на основе BlockingCollection с тремя этапами — ввод-вывод (последовательный), обработка (параллельный), ввод-вывод (последовательный) — возможно, но не обязательно, с общей блокировкой между этапами 1 и 3 (если показано как полезное при сравнительном анализе производительности). Я был бы первым, кто поддержал бы это, поскольку я действительно считаю, что люди должны больше использовать хорошие шаблоны параллельного программирования. - person Kirill Shlenskiy; 24.11.2013
comment
@Кирилл Шленский спасибо за ссылку на книгу - я просто новичок в программировании - похоже чем больше читаю, тем больше понимаю, что ничего не знаю =) - person hbk; 24.11.2013
comment
Очень интересно. Я работаю над решением, которое подойдет! :-) - person Dennis Ziolkowski; 24.11.2013
comment
Мое исследование дало следующий результат: самое быстрое - это сделать нормальный foreach, который будет открывать, манипулировать и закрывать файл после и после. Трехэтапный метод (сначала открыть ряд файлов, параллельно манипулировать ими, закрыть их) потребует слишком много взаимодействия между выделением и освобождением вещей. - person Dennis Ziolkowski; 24.11.2013

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

Блокирует ли Parallel.ForEach?

person ChrisK    schedule 23.11.2013