Ожидание длительного процесса и все еще обновление пользовательского интерфейса

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

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

Я попытался создать процесс для запуска во втором потоке и дождаться его завершения, а также с помощью 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), чтобы он не был заблокирован.

Что я делаю неправильно?

Ответ 1

С# использует модель события - то, что вы хотите сделать, - это отправить процесс, который выполняет эту работу, а затем запустить этот процесс при выполнении индивидуального события или использовать одно из событий потоковой передачи. Хотя процесс запускается в "фоновом" управлении выпуском обратно в систему из вашего кода.

Ответ 2

Во-первых, почему вы хотите избежать DoEvents()?

Во-вторых, вы используете конфликтующие термины.

Ожидание == блокировка

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

Если вы хотите, чтобы пользовательский интерфейс был действительно полезным (не заблокирован), вы не ждете завершения своей задачи. Просто зарегистрируйте обработчик событий, чтобы он срабатывал, когда он заканчивается. Например, с 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 раз в секунду (или больше).

(Вероятно, вы захотите добавить специальную обработку для разных состояний завершения задачи, но это вне темы.)

Ответ 3

Вы не можете ждать в потоке пользовательского интерфейса.

Вместо этого добавьте обработчик в Exited событие.

Ответ 4

Я не знаю, упростит ли это вашу проблему, но мы используем элементы управления Essential Objects: http://www.essentialobjects.com/Products/EOWeb/ для управления нашими длительными процессами.

Ответ 5

Если вы делегируете длинную операцию db в поток фонового рабочего, тогда прогресс сигнализируется путем запуска ProgressChangedEvent из рабочего, вы обрабатываете сообщение о том, что обновляете индикатор выполнения в пользовательском интерфейсе. Аналогично завершено сигнализация путем запуска RunWorkerCompleteEvent.

Не требуется опрос/цикл. Требуются ли события.

Вопрос заключается в том, что ваш фоновый поток делает что-то, что вам не разрешено делать в форме.

Закройте его, измените поле редактирования? и т.п. То, что сделано через какой-то государственный автомат, может быть таким простым, как вы отключите кнопку, когда вы отключаете поток, а затем снова включаете его в событие RunWorkerCompleted. Из вас можно оставить бутум один и проверить булевое имя, занятое в нем. Обработчик кликов.

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

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