Как правильно остановить BackgroundWorker - PullRequest
57 голосов
/ 19 января 2011


У меня есть форма с двумя выпадающими списками. И я хочу заполнить combobox2.DataSource на основе combobox1.Text и combobox2.Text (я предполагаю, что пользователь завершил ввод в combobox1 и находится в середине ввода в combobox2). Итак, у меня есть обработчик событий для combobox2, например:

private void combobox2_TextChanged(object sender, EventArgs e)
{
    if (cmbDataSourceExtractor.IsBusy)
       cmbDataSourceExtractor.CancelAsync();

    var filledComboboxValues = new FilledComboboxValues{ V1 = combobox1.Text,
       V2 = combobox2.Text};
    cmbDataSourceExtractor.RunWorkerAsync(filledComboboxValues );
}

Поскольку сборка DataSource занимает много времени (он создает запрос к базе данных и выполняет его), я решил, что лучше выполнить его в другом процессе с использованием BackgroundWorker. Таким образом, существует сценарий, когда cmbDataSourceExtractor не завершил свою работу и пользователь вводит еще один символ. В этом случае я получаю исключение в этой строке
cmbDataSourceExtractor.RunWorkerAsync(filledComboboxValues ); о том, что BackgroundWorker занят и не может выполнять несколько действий одновременно.
Как избавиться от этого исключения?
Заранее спасибо!

Ответы [ 8 ]

88 голосов
/ 19 января 2011

CancelAsync на самом деле не прерывает вашу ветку или что-то в этом роде. Он отправляет сообщение в рабочий поток, что работа должна быть отменена через BackgroundWorker.CancellationPending. Ваш делегат DoWork, который выполняется в фоновом режиме, должен периодически проверять это свойство и обрабатывать само отмену.

Сложность в том, что ваш делегат DoWork, вероятно, блокируется, а это означает, что работа, которую вы выполняете над своим источником данных, должна быть завершена, прежде чем вы сможете что-либо сделать (например, проверить CancellationPending). Возможно, вам придется перенести вашу фактическую работу в еще одного асинхронного делегата (или, что еще лучше, отправить работу в ThreadPool) и провести опрос основного рабочего потока, пока этот внутренний рабочий поток не вызовет состояние ожидания, ИЛИ не обнаружит CancellationPending .

http://msdn.microsoft.com/en-us/library/system.componentmodel.backgroundworker.cancelasync.aspx

http://www.codeproject.com/KB/cpp/BackgroundWorker_Threads.aspx

28 голосов
/ 02 октября 2011

Если вы добавите цикл между CancelAsync () и RunWorkerAsync () примерно так, это решит вашу проблему

 private void combobox2_TextChanged(object sender, EventArgs e)
 {
     if (cmbDataSourceExtractor.IsBusy)
        cmbDataSourceExtractor.CancelAsync();

     while(cmbDataSourceExtractor.IsBusy)
        Application.DoEvents();

     var filledComboboxValues = new FilledComboboxValues{ V1 = combobox1.Text,
        V2 = combobox2.Text};
     cmbDataSourceExtractor.RunWorkerAsync(filledComboboxValues );
  }

Цикл while с вызовом Application.DoEvents () будет мешать выполнению вашего нового рабочего потока до тех пор, пока текущий поток не будет должным образом отменен, имейте в виду, что вам все равно нужно обрабатывать отмену вашего рабочего потока. С чем-то вроде:

 private void cmbDataSourceExtractor_DoWork(object sender, DoWorkEventArgs e)
 {
      if (this.cmbDataSourceExtractor.CancellationPending)
      {
          e.Cancel = true;
          return;
      }
      // do stuff...
 }

Application.DoEvents () в первом фрагменте кода будет продолжать обрабатывать вашу очередь сообщений потоков GUI, поэтому будет отменено даже отмену и обновление свойства cmbDataSourceExtractor.IsBusy (если вы просто добавили продолжение вместо Application. Цикл DoEvents () блокирует поток графического интерфейса в занятом состоянии и не обрабатывает событие для обновления cmbDataSourceExtractor.IsBusy)

6 голосов
/ 19 января 2011

Вам нужно будет использовать флаг, общий для основного потока и BackgroundWorker, например BackgroundWorker.CancellationPending. Если вы хотите, чтобы BackgroundWorker завершил работу, просто установите флаг, используя BackgroundWorker.CancelAsync ().

MSDN имеет образец: http://msdn.microsoft.com/en-us/library/system.componentmodel.backgroundworker.cancellationpending.aspx

3 голосов
/ 07 июня 2014

МОЙ пример. DoWork ниже:

    DoLengthyWork();

    //this is never executed
    if(bgWorker.CancellationPending)
    {
        MessageBox.Show("Up to here? ...");
        e.Cancel = true;
    }

внутри DoLenghtyWork:

public void DoLenghtyWork()
{
    OtherStuff();
    for(int i=0 ; i<10000000; i++) 
    {  int j = i/3; }
}

внутри OtherStuff ():

public void OtherStuff()
{
    for(int i=0 ; i<10000000; i++) 
    {  int j = i/3; }
}

Что вы хотите сделать, так это изменить DoLenghtyWork и OtherStuff () так, чтобы они стали:

public void DoLenghtyWork()
{
    if(!bgWorker.CancellationPending)
    {              
        OtherStuff();
        for(int i=0 ; i<10000000; i++) 
        {  
             int j = i/3; 
        }
    }
}

public void OtherStuff()
{
    if(!bgWorker.CancellationPending)
    {  
        for(int i=0 ; i<10000000; i++) 
        {  
            int j = i/3; 
        }
    }
}
1 голос
/ 14 апреля 2016

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

private void combobox2_TextChanged(object sender, EventArgs e)
 {
  //Stop the thread here with this
     ThreadWatcher.StopThread = true;//the rest of this thread will run normally after the database function has stopped.
     if (cmbDataSourceExtractor.IsBusy)
        cmbDataSourceExtractor.CancelAsync();

     while(cmbDataSourceExtractor.IsBusy)
        Application.DoEvents();

     var filledComboboxValues = new FilledComboboxValues{ V1 = combobox1.Text,
        V2 = combobox2.Text};
     cmbDataSourceExtractor.RunWorkerAsync(filledComboboxValues );
  }

все хорошо

private void cmbDataSourceExtractor_DoWork(object sender, DoWorkEventArgs e)
 {
      if (this.cmbDataSourceExtractor.CancellationPending)
      {
          e.Cancel = true;
          return;
      }
      // do stuff...
 }

Теперь добавьте следующий класс

public static class ThreadWatcher
{
    public static bool StopThread { get; set; }
}

и в вашем классе, где вы читаете базу данных

List<SomeObject>list = new List<SomeObject>();
...
if (!reader.IsDbNull(0))
    something = reader.getString(0);
someobject = new someobject(something);
if (ThreadWatcher.StopThread == true)
    break;
list.Add(something);
...

не забудьте использовать блок finally для правильного закрытия соединения с базой данных и т. Д. Надеюсь, это поможет! Пожалуйста, отметьте меня, если вы найдете это полезным.

1 голос
/ 16 марта 2016

В моем случае мне пришлось объединить базу данных для подтверждения оплаты, а затем обновить WPF UI.

Механизм, который раскручивает все процессы:

public void Execute(object parameter)
        {
            try
            {
                var url = string.Format("{0}New?transactionReference={1}", Settings.Default.PaymentUrlWebsite, "transactionRef");
                Process.Start(new ProcessStartInfo(url));
                ViewModel.UpdateUiWhenDoneWithPayment = new BackgroundWorker {WorkerSupportsCancellation = true};
                ViewModel.UpdateUiWhenDoneWithPayment.DoWork += ViewModel.updateUiWhenDoneWithPayment_DoWork;
                ViewModel.UpdateUiWhenDoneWithPayment.RunWorkerCompleted += ViewModel.updateUiWhenDoneWithPayment_RunWorkerCompleted;
                ViewModel.UpdateUiWhenDoneWithPayment.RunWorkerAsync();
            }
            catch (Exception e)
            {
                ViewModel.Log.Error("Failed to navigate to payments", e);
                MessageBox.Show("Failed to navigate to payments");
            }
        }

Механизм проверки выполнения:

 private void updateUiWhenDoneWithPayment_DoWork(object sender, DoWorkEventArgs e)
    {
        Thread.Sleep(30000);
        while (string.IsNullOrEmpty(GetAuthToken()) && !((BackgroundWorker)sender).CancellationPending)
        {
            Thread.Sleep(5000);
        }

        //Plug in pooling mechanism
        this.AuthCode = GetAuthToken();
    }

Механизм, который отменяется, если окно закрывается:

private void PaymentView_OnUnloaded(object sender, RoutedEventArgs e)
    {
        var context = DataContext as PaymentViewModel;
        if (context.UpdateUiWhenDoneWithPayment != null && context.UpdateUiWhenDoneWithPayment.WorkerSupportsCancellation && context.UpdateUiWhenDoneWithPayment.IsBusy)
            context.UpdateUiWhenDoneWithPayment.CancelAsync();
    }
1 голос
/ 19 января 2011

Проблема вызвана тем, что cmbDataSourceExtractor.CancelAsync() является асинхронным методом, операция Cancel еще не завершена, когда cmdDataSourceExtractor.RunWorkerAsync(...) exitst. Вам следует дождаться завершения cmdDataSourceExtractor перед повторным вызовом RunWorkerAsync. Как это сделать, объясняется в этом вопросе .

0 голосов
/ 16 апреля 2012

Я согласен с парнями. Но иногда вам нужно добавить больше вещей.

IE

1) Добавить это worker.WorkerSupportsCancellation = true;

2) Добавьте к вашему классу какой-нибудь метод для выполнения следующих действий

public void KillMe()
{
   worker.CancelAsync();
   worker.Dispose();
   worker = null;
   GC.Collect();
}

Так что, прежде чем закрыть приложение, вы должны вызвать этот метод.

3) Возможно, вы можете Dispose, null все переменные и таймеры, которые находятся внутри BackgroundWorker.

...