Как правильно остановить 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 ответов:
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(); }