Ожидание долгого процесса и все еще обновление пользовательского интерфейса - PullRequest
3 голосов
/ 16 ноября 2011

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

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

Я попытался создать процесс для запуска во 2-м потоке и ожидания его завершения, а также с помощью BackgroundWorker.

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

По сути, сейчас я делаю следующее:

  1. Подключение к базе данных
  2. Создайте фоновый рабочий (или поток) для записи в базу данных (я, вероятно, в итоге получу BackgroundWorker, чтобы я мог использовать ReportProgress
  3. Начать тему или BackgroundWorker
  4. Используйте цикл While для ожидания завершения потока / BackgroundWorker. Для потока я жду, пока IsAlive станет ложным, для BackgroundWorker я переключу логическую переменную.
  5. Я сообщаю пользователю, что процесс завершен.

Проблема в # 4.

Выполнение цикла while без кода или Thread.Sleep(0) оставляет пользовательский интерфейс заблокированным (Thread.Sleep(0) заставляет программу также брать 100% ресурсов программы)

Итак, я делаю:

while (!thread.IsAlive)
   Thread.Sleep(1);

-или-

while (bProcessIsRunning)
   Thread.Sleep(1);

, который блокирует пользовательский интерфейс.

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

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

Что я делаю не так?

Ответы [ 5 ]

5 голосов
/ 17 ноября 2011

Во-первых, почему вы хотите избежать 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 раз в секунду (или более).

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

4 голосов
/ 16 ноября 2011

C # использует модель событий - вам нужно отправить процесс, который выполняет эту работу, и затем заставить этот процесс запускать пользовательское событие по завершении или использовать одно из потоковых событий. Пока процесс выполняется в «фоновом» режиме, отпустите обратно в систему из вашего кода.

0 голосов
/ 17 ноября 2011

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

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

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

Закрыть, изменить окно редактирования?и т. д. Это делается с помощью какого-то конечного автомата. Это может быть что-то простое: вы отключаете кнопку, когда запускаете поток, а затем снова включаете ее в событии RunWorkerCompleted.Вы можете оставить буутом в покое и проверить булево значение, называемое занятое, в его обработчике кликов.

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

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

0 голосов
/ 16 ноября 2011

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

0 голосов
/ 16 ноября 2011

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

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

...