it-swarm.com.ru

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

55
StuffHappens

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

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

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

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

87
HackedByChinese

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

26
jenovachild

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

В MSDN есть образец: http://msdn.Microsoft.com/en-us/library/system.componentmodel.backgroundworker.cancellationpending.aspx

6
Daniel Gehriger

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

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

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

1
Bas Bossink

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

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

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

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

IE

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

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

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

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

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

0
Academy of Programmer