Подтвердить что ты не робот

Как правильно остановить BackgroundWorker


У меня есть форма с двумя комбобоксами. И я хочу заполнить 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 занят и не может выполнить несколько действий в одно и то же время.
Как избавиться от этого исключения?
Спасибо заранее!

4b9b3361

Ответ 1

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

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

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

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

Ответ 2

Если вы добавите цикл между 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 будет по-прежнему обрабатываться (если вы просто добавили продолжение вместо приложения. DoEvents() цикл блокирует поток GUI в состоянии занятости и не будет обрабатывать событие для обновления cmbDataSourceExtractor.IsBusy)

Ответ 3

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

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

Ответ 4

МОЙ пример. 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; 
        }
    }
}

Ответ 5

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

Ответ 6

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

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

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();
    }

Ответ 7

Мой ответ немного другой, потому что я пробовал эти методы, но они не работали. В моем коде используется дополнительный класс, который проверяет флаг Boolean в общедоступном статическом классе по мере чтения значений базы данных или где я предпочитаю его непосредственно перед тем, как объект добавляется в объект 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, чтобы правильно закрыть соединение с базой данных и т.д. Надеюсь, это поможет! Пожалуйста, пометьте меня, если вы сочтете это полезным.

Ответ 8

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

IE

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

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

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

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

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