我有一个有2个组合框的表格。我想基于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正忙,无法在同一时间执行多项操作
如何摆脱这种例外?
提前谢谢!
答案 0 :(得分:88)
CancelAsync
实际上并没有中止你的线程或类似的东西。它向工作线程发送一条消息,告知应该通过BackgroundWorker.CancellationPending
取消工作。您在后台运行的DoWork委托必须定期检查此属性并自行处理取消。
棘手的部分是您的DoWork委托可能已阻止,这意味着您在DataSource上所做的工作必须先完成,然后才能执行其他操作(例如检查CancellationPending)。您可能需要将实际工作移动到另一个异步委托(或者更好的是,将工作提交到ThreadPool
),并让您的主工作线程轮询,直到此内部工作线程触发等待状态,或者它检测CancellationPending。
http://msdn.microsoft.com/en-us/library/system.componentmodel.backgroundworker.cancelasync.aspx
http://www.codeproject.com/KB/cpp/BackgroundWorker_Threads.aspx
答案 1 :(得分:27)
如果你在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 );
}
调用Application.DoEvents()的while循环将导致新工作线程的执行,直到当前的线程被正确取消,请记住,您仍然需要处理工作线程的取消。有类似的东西:
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)
答案 2 :(得分:6)
您必须使用主线程和BackgroundWorker之间共享的标志,例如BackgroundWorker.CancellationPending
。如果希望BackgroundWorker退出,只需使用BackgroundWorker.CancelAsync()设置标志。
答案 3 :(得分:3)
我的例子。 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;
}
}
}
答案 4 :(得分:1)
问题是由cmbDataSourceExtractor.CancelAsync()
是异步方法,Cancel
操作在cmdDataSourceExtractor.RunWorkerAsync(...)
出口时尚未完成的事实引起的。在再次致电cmdDataSourceExtractor
之前,您应该等待RunWorkerAsync
完成。如何解释in this SO question。
答案 5 :(得分:1)
在我的情况下,我必须汇总数据库以进行付款确认,然后更新property
用户界面。
推动所有流程的机制:
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();
}
答案 6 :(得分:1)
我的回答有点不同,因为我尝试过这些方法,但它们没有用。我的代码使用一个额外的类来检查公共静态类中的布尔标志,因为在将对象添加到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块来正确关闭数据库连接等。希望这会有所帮助!如果您觉得有用,请注意我。
答案 7 :(得分:0)
我同意这些人的意见。但有时你必须添加更多东西。
IE
1)添加此worker.WorkerSupportsCancellation = true;
2)为你的课程添加一些方法来做以下事情
public void KillMe()
{
worker.CancelAsync();
worker.Dispose();
worker = null;
GC.Collect();
}
因此,在关闭您的申请之前,您必须调用此方法。
3)您可以Dispose, null
BackgroundWorker
内的所有变量和计时器。{3>}。