Как мне уступить потоку пользовательского интерфейса для обновления пользовательского интерфейса при выполнении пакетной обработки в приложении WinForm?

StackOverflow https://stackoverflow.com/questions/106036

  •  01-07-2019
  •  | 
  •  

Вопрос

У меня есть приложение WinForms, написанное на C # с .NET 3.5.Он выполняет длительный пакетный процесс.Я хочу, чтобы приложение обновляло статус того, что делает пакетный процесс.Каков наилучший способ обновить пользовательский интерфейс?

Это было полезно?

Решение

BackgroundWorker звучит как объект, который вам нужен.

Другие советы

Самый быстрый и грязный способ - это использование Application.DoEvents() Но это может вызвать проблемы с порядком обработки событий.Так что это не рекомендуется

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

Запустите длительный процесс в фоновом потоке.Класс background worker - простой способ сделать это - он обеспечивает простую поддержку отправки обновлений хода выполнения и событий завершения, для которых обработчики событий вызываются в нужном вам потоке.Это позволяет сохранить код чистым и лаконичным.

Для отображения обновлений используются индикаторы выполнения или текст в строке состояния - два наиболее распространенных подхода.

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

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

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

К сожалению, экран не просто рисуется, он также реагирует на действия пользователя.Это связано с тем, что DoEvents останавливает то, что вы делаете сейчас, для обработки всех сообщений Windows, ожидающих обработки вашим приложением Winforms.Эти сообщения включают запросы на перерисовку, а также на любой ввод текста пользователем, нажатие кнопки и т.д.

Так, например, пока вы сохраняете данные, пользователь может выполнять такие действия, как отображение в приложении модального диалогового окна, которое совершенно не связано с длительной задачей (например, Справка-> О программе).Теперь вы реагируете на новые действия пользователя внутри уже запущенная давно выполняемая задача.DoEvents вернется, когда все события, которые ожидали, когда вы его вызывали, будут завершены, и тогда ваша длительная задача будет продолжена.

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

То, что здесь происходит, - это Приложение.DoEvents делает ваш код реентерабельным.Смотрите определение в Википедии здесь.Обратите внимание на некоторые моменты из верхней части статьи, что для того, чтобы код был реентерабельным, он:

  • Не должно содержать статических (или глобальных) непостоянных данных.
  • Должен работать только с данными, предоставленными ему вызывающим абонентом.
  • Не следует полагаться на блокировки для одноэлементных ресурсов.
  • Не должен вызывать нереентерабельные компьютерные программы или подпрограммы.

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

Как говорят многие люди здесь, DoEvents могут привести к некоторым очень странным сценариям в коде.Ошибки, к которым это может привести, может быть очень трудно диагностировать, и ваш пользователь вряд ли скажет вам: "О, возможно, это произошло из-за того, что я нажал эту несвязанную кнопку, пока ждал ее сохранения".

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

Я хочу еще раз повторить то, что отметили мои предыдущие комментаторы:пожалуйста, по возможности избегайте DoEvents(), так как это почти всегда является формой "взлома" и вызывает кошмары при обслуживании.

Если вы пойдете по пути BackgroundWorker (который я предлагаю), вам придется иметь дело с многопоточными вызовами пользовательского интерфейса, если вы хотите вызвать какие-либо методы или свойства элементов управления, поскольку они зависят от потока и должны вызываться только из потока, в котором они были созданы.Используйте элемент управления.Invoke() и / или Control.BeginInvoke() в зависимости от обстоятельств.

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

Control.Invoke является синхронным (ожидает, пока делегат не вернется).Если вы не хотите ждать, вы используете .BeginInvoke() только для того, чтобы поставить команду в очередь.

Возвращаемое значение .BeginInvoke() позволяет вам проверить, завершен ли метод, или дождаться его завершения.

Использование Backgroundworker, и если вы также пытаетесь обновить поток графического интерфейса, обрабатывая ProgressChanged событие (например, для ProgressBar), обязательно также установите WorkerReportsProgress=true, или поток , сообщающий о ходе выполнения , умрет при первой попытке вызова ReportProgress...

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

Приложение.DoEvents() или, возможно, запустить пакет в отдельном потоке?

DoEvents() - это то, что я искал, но я также проголосовал за ответы backgroundworker, потому что это выглядит как хорошее решение, которое я изучу подробнее.

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top