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



У меня есть форма с 2 comboboxes на нем. И я хочу заполнить 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 );
}

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

8 55

8 ответов:

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

Если вы добавите цикл между 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 с вызовом приложения.DoEvents () будет выполнять выполнение вашего нового рабочего потока до тех пор, пока текущий не будет отменен должным образом, имейте в виду, что вам все еще нужно обрабатывать отмену вашего рабочего потока. С чем-то вроде:

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

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

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

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

мой пример . 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; 
        }
    }
}

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

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

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

IE

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

2) Добавить в класс метод, чтобы сделать следующие вещи

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

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

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

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

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

public void Execute(object parameter)
        {
            try
            {
                var amount = ViewModel.Amount;
                var transactionId = ViewModel.TransactionMain.TransactionId.ToString();
                var productCode = ViewModel.TransactionMain.TransactionDetailList.First().Product.ProductCode;
                var transactionReference = GetToken(amount, transactionId, productCode);
                var url = string.Format("{0}New?transactionReference={1}", Settings.Default.PaymentUrlWebsite, transactionReference);
                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();
    }