Как я могу ждать завершения BackgroundWorker? - PullRequest
1 голос
/ 18 января 2010

Я хотел бы использовать BackgroundWorker для выполнения транзакции базы данных из графического интерфейса.

Как я могу дать команду BackgroundWorker выполнить работу, а затем ПОДОЖДИТЕ, чтобы рабочий завершил работу, сохраняя при этом графический интерфейс?

Должен ли я использовать DoEvents для этой цели или есть другой способ?

Ответы [ 4 ]

10 голосов
/ 18 января 2010

Спрашивая: «Как я могу дать команду BackgroundWorker выполнить работу, а затем ПОДОЖДИТЬ, чтобы рабочий завершил работу, сохраняя при этом графический интерфейс?» действительно спрашивает "Как я могу использовать BackgroundWorker?". Вот что BackgroundWorker делает .

Когда вы пишете фоновое задание, вы в основном разбиваете метод на четыре части:

  1. Подготовка к запуску задачи.
  2. Сама задача.
  3. Сообщение о прогрессе в пользовательский интерфейс во время выполнения задачи.
  4. Очистить вещи, когда задача выполнена.

Итак, вам нужно написать четыре метода. Первый - это метод, который создает BackgroundWorker, добавляет обработчики событий к его событиям DoWork, ProgressChanged и RunWorkerCompleted, переводит пользовательский интерфейс в любое состояние, в котором он должен находиться во время выполнения задачи, и вызывает RunWorkerAsync чтобы запустить задачу.

Остальные три являются этими тремя обработчиками событий. DoWork - это DoWorkEventHandler, который выполняет эту работу и вызывает ReportProgress всякий раз, когда ему необходимо сообщить о своем прогрессе в пользовательский интерфейс. ProgressChanged - это ProgressChangedEventHandler, который фактически обновляет интерфейс при вызове ReportProgress. И RunWorkerCompleted - это RunWorkerCompletedEventHandler, который сообщает UI, что работа выполнена.

Вот и все.

Ну, не совсем все . Во-первых, вы должны убедиться, что вы проверили и обработали свойство Error в вашем обработчике завершения. Если вы этого не сделаете, у вас не будет возможности узнать, что ваш метод do-work вызвал исключение, так как это не происходит в потоке пользовательского интерфейса и, следовательно, не вызывает исключение, которое вы видите. (Он отобразится в окне «Вывод», если вы ищете его.)

Во-вторых, вы должны убедиться, что DoWorkEventHandler ничего не касается в пользовательском интерфейсе. Это может быть сложно, если вы используете шаблон MVVM, и вы не запланировали эту непредвиденную ситуацию, потому что благодаря магии привязки данных у вас, вероятно, есть вещи в вашей модели, которые обновляют представления, с которыми связан пользовательский интерфейс, что означает манипулирование моделью в вашем рабочем методе - это манипулирование пользовательским интерфейсом. Предполагая, что вы реализуете INotifyPropertyChanged, хорошим способом избежать проблем здесь является создание механизма, при котором ваши представления не генерируют события PropertyChanged во время выполнения фоновой задачи.

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

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

2 голосов
/ 18 января 2010

Я обычно жду RunWorkerCompleted в отдельной модальной форме с индикатором выполнения.Это заставляет конечного пользователя ждать завершения вашей операции, но графический интерфейс реагирует таким образом, что вы можете перемещать формы и т.д.ему / ей выполнять только определенные операции, которые не могут нарушить логику вашей программы.

Это то, что вы хотите?

1 голос
/ 18 января 2010

Вы можете вызвать RunWorkerAsync следующим образом:

this.backgroundWorker1.RunWorkerAsync();

И тогда вам нужно будет использовать событие RunWorkerCompleted .

Вот пример из MSDN:

private void backgroundWorker1_RunWorkerCompleted(
    object sender, RunWorkerCompletedEventArgs e)
{
    // First, handle the case where an exception was thrown.
    if (e.Error != null)
    {
        MessageBox.Show(e.Error.Message);
    }
    else if (e.Cancelled)
    {
        // Next, handle the case where the user canceled 
        // the operation.
        // Note that due to a race condition in 
        // the DoWork event handler, the Cancelled
        // flag may not have been set, even though
        // CancelAsync was called.
        resultLabel.Text = "Canceled";
    }
    else
    {
        // Finally, handle the case where the operation 
        // succeeded.
        resultLabel.Text = e.Result.ToString();
    }

    // Enable the UpDown control.
    this.numericUpDown1.Enabled = true;

    // Enable the Start button.
    startAsyncButton.Enabled = true;

    // Disable the Cancel button.
    cancelAsyncButton.Enabled = false;
}

И это все. Я не думаю, что есть необходимость звонить в DoEvents. Пользовательский интерфейс по-прежнему должен реагировать, пока работает BackgroundWorker.

1 голос
/ 18 января 2010

Вы используете:

Application.DoEvents();

но вы должны прочитать эту ссылку для лучшего способа обновления пользовательского интерфейса из потока фонового рабочего

В основном код выглядит так:

// The declaration of the textbox.
private TextBox m_TextBox;

// Updates the textbox text.
private void UpdateText(string text)
{
    // Set the textbox text.
    m_TextBox.Text = text;
}


public delegate void UpdateTextCallback(string text);

Затем в вашем потоке вы можете вызвать метод Invoke для m_TextBox, передав делегат для вызова, а также параметры.

m_TextBox.Invoke(new UpdateTextCallback(this.UpdateText),
                 new object[]{”Text generated on non-UI thread.”});

Читайте эту ссылку также, для получения дополнительной информации

...