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

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

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

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

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

В общем, сейчас я делаю следующее:

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

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

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

So I do:

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

-or-

while (bProcessIsRunning)
   Thread.Sleep(1);

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

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

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

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


person Trevor Watson    schedule 16.11.2011    source источник
comment
Добро пожаловать в Переполнение стека. Пара мелочей: 1. Пожалуйста, не добавляйте в заголовки такие префиксы, как C# .NET (3.0). Для этого используются теги в Stack Overflow. 2. Пожалуйста, без подписей. 3. Говоря о тегах, вы должны сообщить нам, используете ли вы winforms или wpf или что-то еще, добавив соответствующий тег.   -  person John Saunders    schedule 16.11.2011
comment
Извините, я не был уверен, чем его пометить, а с шапкой из 5 тегов я не поставил его. Я использую Winforms (теперь это тег)   -  person Trevor Watson    schedule 16.11.2011
comment
@JohnSaunders: я категорически не согласен с вашим предложением не включать теги (например, C# .NET WinForms) в заголовок. ПРИЧИНА: Google — это наиболее распространенный способ, с помощью которого люди попадают на страницу stackoverflow. Наличие заголовка, включающего такой контекст, позволяет НАМНОГО быстрее решить, уместны ли вопросы и ответы.   -  person ToolmakerSteve    schedule 24.06.2014
comment
@ToolmakerSteve: ты ошибаешься. Stack Overflow уже помещает верхний тег в заголовок, как его видит Google.   -  person John Saunders    schedule 24.06.2014
comment
@JohnSaunders: Ну и что. (1) одного тега недостаточно. (2) если вы по какой-либо причине перешли на страницу, теперь вы смотрите на сам заголовок. Почему, во имя бога, вы хотите уменьшить полезность названия? Неправильный.   -  person ToolmakerSteve    schedule 24.06.2014
comment
@ToolmakerSteve: прочитайте статью по ссылке. По этому поводу и по поводу лишнего шума в целом существует довольно четкое согласие. И я, и многие другие считают, что добавление метаданных к заголовку снижает ценность в остальном ясного заголовка. Зачем переходить от ожидания долгого процесса и обновления пользовательского интерфейса к C# Winforms, ожидающего длительного процесса и обновления пользовательского интерфейса?   -  person John Saunders    schedule 24.06.2014
comment
@JohnSaunders: ЕСЛИ бы теги были перечислены сразу под заголовком, это было бы разумно. Но это не так. ЭТО НЕ ШУМ! Представление о том, что нужно просмотреть до конца, возможно, очень длинный вопрос, чтобы выяснить ключевой контекст вопроса, чтобы узнать, волнует ли вас вопрос или нет, просто ошибочно.   -  person ToolmakerSteve    schedule 24.06.2014


Ответы (5)


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

person Hogan    schedule 16.11.2011
comment
Означает ли это, что я должен запустить BackgroundWorker, а затем оставить выполнение всего кода до тех пор, пока не сработает RunWorkerCompleted? Разве это не означает, что пользователь по-прежнему может взаимодействовать с формой (так же, как с DoEvents) во время выполнения процесса? Это также увеличивает потребность в глобальных переменных (таких как соединение с базой данных) и потребует значительно больше кода, если вызывающие функции находятся в классе, а не в самой форме. - person Trevor Watson; 16.11.2011
comment
Есть несколько способов справиться с этим. Легко отключить интерфейс и поставить счетчик или какой-либо другой визуальный элемент. Затем разорвите его, когда сработает асинхронность. Другие варианты — только заморозить элементы управления или по-разному обрабатывать ответы во время работы в фоновом режиме. - person Hogan; 16.11.2011
comment
Я не знаю, почему для этого потребуется больше кода, должно быть так, что замыкания должны позволять вам использовать локальные переменные в вашем потоке. Хотели бы вы увидеть пример этого? - person Hogan; 16.11.2011
comment
Я создал события в своем классе, которые используют делегатов BackgroundWorker. Таким образом, я могу просто передавать информацию без какой-либо обработки. Я создам индикатор выполнения или счетчик для формы, пока процесс идет. Спасибо! - person Trevor Watson; 16.11.2011
comment
@TrevorWatson Точно. Идеальный. Удачи. - person Hogan; 17.11.2011

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

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

person Igby Largeman    schedule 16.11.2011

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

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

person SLaks    schedule 16.11.2011

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

person drdwilcox    schedule 16.11.2011
comment
К сожалению, в настоящее время мы не можем приобрести какие-либо дополнительные компоненты. Хотя мне нравятся некоторые компоненты их веб-интерфейса. - person Trevor Watson; 16.11.2011
comment
Компонент прогресса бесплатен. - person drdwilcox; 17.11.2011

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

Нет необходимости в опросе/зацикливании Do-событий.

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

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

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

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

person Tony Hopkinson    schedule 16.11.2011
comment
В основном это связано с сообщением «Окно не отвечает», но в какой-то момент пользователю должно быть какое-то уведомление о том, что процесс запущен, чтобы он не подумал, что мы разбились, я полагаю. - person Trevor Watson; 17.11.2011