Ожидание долгого процесса и все еще обновление пользовательского интерфейса
Я пытался создать задачу, которая пишет в базу данных, не блокируя поток пользовательского интерфейса. Самая большая проблема, с которой я столкнулся - это ожидание завершения этого процесса без блокировки.
Я пытался избежать использования DoEvents
(хотя он довольно часто используется в этой программе прямо сейчас, я бы хотел отказаться от его использования, двигаясь вперед).
Я попытался создать процесс для запуска во 2-м потоке и ждать его завершения, а также с помощью BackgroundWorker
,
Проблема, с которой я столкнулся, заключается в том, что код не запускается в другом потоке, а пытается найти способ дождаться его завершения.
По сути, сейчас я делаю следующее:
- Подключиться к базе данных
- Создайте фоновый рабочий (или поток) для записи в базу данных (я, вероятно, в конечном итоге
BackgroundWorker
так что я могу использоватьReportProgress
- Начать тему или
BackgroundWorker
- Используйте цикл While для ожидания потока /
BackgroundWorker
заканчивать. За нить ждуIsAlive
стать ложным, дляBackgroundWorker
Я переключаю логическую переменную. - Я сообщаю пользователю, что процесс завершен.
Проблема в #4.
Выполнение цикла while без кода или Thread.Sleep(0)
оставляет интерфейс заблокированным (Thread.Sleep(0)
заставляет программу также брать 100% ресурсов программы)
Итак, я делаю:
while (!thread.IsAlive)
Thread.Sleep(1);
-или же-
while (bProcessIsRunning)
Thread.Sleep(1);
который блокирует пользовательский интерфейс.
Если я позвоню Application.DoEvents()
там пользовательский интерфейс обновляется (хотя он и активен, поэтому я должен отключить всю форму во время выполнения этого процесса).
Если я запускаю процесс синхронно, мне все равно нужно создать какой-то способ обновления пользовательского интерфейса (на мой взгляд, DoEvents
звоните), чтобы он не казался заблокированным.
Что я делаю неправильно?
5 ответов
В C# используется модель событий - вам нужно отправить процесс, который выполняет эту работу, и затем заставить этот процесс запускать пользовательское событие по завершении или использовать одно из потоковых событий. Пока процесс работает в "фоновом" режиме, отпустите обратно в систему из вашего кода.
Во-первых, почему вы хотите избежать DoEvents()
?
Во-вторых, вы используете противоречивые термины.
ожидание == блокировка
Вы говорите, что не хотите блокировать поток пользовательского интерфейса, но хотите дождаться завершения задачи. Это взаимоисключающие государства. Если вы ждете, когда что-то закончится, вы блокируете свою ветку.
Если вы хотите, чтобы пользовательский интерфейс действительно использовался (не блокировался), то вы не ждите завершения своей задачи. Просто зарегистрируйте обработчик события, чтобы он срабатывал после его завершения. Например, с помощью BackgroundWorker обработайте событие RunWorkerCompleted. Для Задачи вы можете использовать продолжение для отправки обратного вызова в ваш основной поток.
Но, похоже, вы просто хотите, чтобы пользовательский интерфейс обновлялся, а не использовался. Обычно это имеет смысл, только если вы хотите, чтобы индикатор выполнения или какая-либо другая анимация пользовательского интерфейса продолжали двигаться. В этом случае я бы открыл модальное диалоговое окно, запустил мою задачу и затем подождал ее, пока, да, вызывал DoEvents().
var dialog = new MessageBoxFormWithNoButtons("Please wait while I flip the jiggamawizzer");
dialog.Shown += (_, __) =>
{
var task = Task.Factory.StartNew(() => WriteToDatabase(), TaskCreationOptions.LongRunning);
while (!task.Wait(50)) // wait for 50 milliseconds (make shorter for smoother UI animation)
Application.DoEvents(); // allow UI to look alive
dialog.Close();
}
dialog.ShowDialog();
Модальное диалоговое окно не позволяет пользователю делать что-либо, но любая анимация все равно будет работать из-за того, что DoEvents () вызывается 20 раз в секунду (или более).
(Возможно, вы захотите добавить специальную обработку для разных состояний завершения задачи, но это не по теме.)
Я не знаю, упростит ли это вашу проблему, но мы используем элементы управления Essential Objects: http://www.essentialobjects.com/Products/EOWeb/ для управления нашими длительными процессами.
Если вы делегируете длинную операцию db потоку фонового работника, то о прогрессе будет сигнализироваться запуском ProgressChangedEvent от worker, вы обрабатываете, например, обновление индикатора выполнения в пользовательском интерфейсе. О том же самом завершении сообщают, запуская RunWorkerCompleteEvent.
Не требуется опрос / зацикливание.
Вопрос в том, что пока ваш фоновый поток делает что-то, что вам не разрешено делать в форме.
Закрыть это, изменить окно редактирования? и т. д. Это делается с помощью какого-то конечного автомата. Это может быть что-то простое: вы отключаете кнопку, когда запускаете поток, а затем снова включаете ее в событии RunWorkerCompleted. Из вас можно оставить буутом в покое и проверить логическое значение "занят" в обработчике кликов.
Вы загружаете процесс, чтобы показать прогресс, или просто избегаете "Windiows не отвечает", или есть другие действия, которые пользователь может делать разумно, пока он находится в середине процесса, такие как закрытие формы, отмена операции и т. Д.
Как только вы выяснили, что должен делать пользовательский интерфейс, вы можете подключить события backgrondworker в некоторый код, который будет управлять всем.
Вы не можете ждать в потоке пользовательского интерфейса.
Вместо этого добавьте обработчик к Exited
событие