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