如何停止BackgroundWorker窗体的closures事件?
我有一个窗体,产生一个BackgroundWorker,应该更新窗体自己的文本框(主线程),因此Invoke((Action) (...));
呼叫。
如果在HandleClosingEvent
我只是做bgWorker.CancelAsync()
然后我得到ObjectDisposedException
Invoke(...)
调用,可以理解的。 但是,如果我坐在HandleClosingEvent
和等待bgWorker完成,比.Invoke(…)永远不会返回,也可以理解。
任何想法如何closures此应用程序没有得到例外,或死锁?
以下是简单的Form1类的3个相关方法:
public Form1() { InitializeComponent(); Closing += HandleClosingEvent; this.bgWorker.RunWorkerAsync(); } private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e) { while (!this.bgWorker.CancellationPending) { Invoke((Action) (() => { this.textBox1.Text = Environment.TickCount.ToString(); })); } } private void HandleClosingEvent(object sender, CancelEventArgs e) { this.bgWorker.CancelAsync(); /////// while (this.bgWorker.CancellationPending) {} // deadlock }
唯一的死锁安全和exception安全的方法来做到这一点,我知道是实际取消FormClosing事件。 如果BGW仍在运行,请设置e.Cancel = true,并设置一个标志来指示用户请求closures。 然后在BGW的RunWorkerCompleted事件处理程序中检查该标志,如果已设置,则调用Close()。
private bool closePending; protected override void OnFormClosing(FormClosingEventArgs e) { if (backgroundWorker1.IsBusy) { closePending = true; backgroundWorker1.CancelAsync(); e.Cancel = true; this.Enabled = false; // or this.Hide() return; } base.OnFormClosing(e); } void backgroundWorker1_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e) { if (closePending) this.Close(); closePending = false; // etc... }
我find了另一种方式。 如果你有更多的背景工作者,你可以:
List<Thread> bgWorkersThreads = new List<Thread>();
在每个后台工作者的DoWork方法中:
bgWorkesThreads.Add(Thread.CurrentThread);
你可以使用的Arter:
foreach (Thread thread in this.bgWorkersThreads) { thread.Abort(); }
我用在控制中的Word加载项,我在CustomTaskPane
使用它。 如果有人closures文件或应用程序提前closures所有我的backgroundWorkes完成他们的工作,它引发了一些COM Exception
(我不记得是什么)。 CancelAsync()
不起作用。
但是有了这个,我可以closuresbackgroundworkers
使用的所有线程立即在DocumentBeforeClose
事件中解决了我的问题。
这是我的解决scheme(对不起,它在VB.Net)。
当我运行FormClosing事件时,运行BackgroundWorker1.CancelAsync()将CancellationPending值设置为True。 不幸的是,程序从来没有真正有机会检查值CancellationPending值设置e.Cancel为真(据我所知,只能在BackgroundWorker1_DoWork中完成)。 我没有删除这条线,虽然它似乎没有什么区别。
我添加了一个将我的全局variablesbClosingForm设置为True的行。 然后,我在BackgroundWorker_WorkCompleted中添加了一行代码,在执行任何结束步骤之前,检查e.Cancelled以及全局variablesbClosingForm。
使用这个模板,即使背景工作者处于某个事物的中间,这个模板也可以随时closures你的表格(这可能不太好,但是一定会发生,所以也可以处理)。 我不确定是否有必要,但是在全部发生之后,您可以完全将Background工作人员置于Form_Closed事件中。
Private bClosingForm As Boolean = False Private Sub SomeFormName_FormClosing(ByVal sender As Object, ByVal e As System.Windows.Forms.FormClosingEventArgs) Handles Me.FormClosing bClosingForm = True BackgroundWorker1.CancelAsync() End Sub Private Sub backgroundWorker1_DoWork(ByVal sender As Object, ByVal e As System.ComponentModel.DoWorkEventArgs) Handles BackgroundWorker1.DoWork 'Run background tasks: If BackgroundWorker1.CancellationPending Then e.Cancel = True Else 'Background work here End If End Sub Private Sub BackgroundWorker1_RunWorkerCompleted(ByVal sender As System.Object, ByVal e As System.ComponentModel.RunWorkerCompletedEventArgs) Handles BackgroundWorker1.RunWorkerCompleted If Not bClosingForm Then If Not e.Cancelled Then 'Completion Work here End If End If End Sub
你不能等待表单析构函数中的信号吗?
AutoResetEvent workerDone = new AutoResetEvent(); private void HandleClosingEvent(object sender, CancelEventArgs e) { this.bgWorker.CancelAsync(); } private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e) { while (!this.bgWorker.CancellationPending) { Invoke((Action) (() => { this.textBox1.Text = Environment.TickCount.ToString(); })); } } private ~Form1() { workerDone.WaitOne(); } void backgroundWorker1_RunWorkerCompleted( Object sender, RunWorkerCompletedEventArgs e ) { workerDone.Set(); }
首先,ObjectDisposedException在这里只是一个可能的缺陷。 运行OP的代码在很多场合下都产生了下面的InvalidOperationException:
在创build窗口句柄之前,不能在控件上调用Invoke或BeginInvoke。
我想这可以通过在Loadedcallback而不是构造函数上启动worker来修改,但是如果使用BackgroundWorker的进度报告机制,这个完整的考虑可以完全避免。 以下运作良好:
private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e) { while (!this.bgWorker.CancellationPending) { this.bgWorker.ReportProgress(Environment.TickCount); Thread.Sleep(1); } } private void bgWorker_ProgressChanged(object sender, ProgressChangedEventArgs e) { this.textBox1.Text = e.ProgressPercentage.ToString(); }
我有种劫持百分比参数,但可以使用其他重载传递任何参数。
有趣的是要注意,消除上述睡眠呼叫会阻塞UI,消耗较高的CPU并不断增加内存的使用。 我想这与GUI的消息队列过载有关。 但是,在睡眠呼叫保持不变的情况下,CPU使用率几乎为零,并且内存使用情况也不错。 为了谨慎起见,应该使用比1 ms更高的值? 这里的专家意见将不胜感激… 更新 :看来,只要更新不是太频繁,应该是OK: 链接
在任何情况下,我都无法预见GUI更新的时间间隔要短于几毫秒(至less在人类正在观看GUI的情况下),所以我认为大部分时间进度报告将是正确的select
你的背景工作者不应该使用Invoke来更新文本框。 它应该问UI线程很好地更新文本框使用事件ProgressChanged的值放在附加的文本框。
在事件closures(或者事件closures)期间,UI线程会记住表单在取消背景工作之前已closures。
在接收到progressChanged后,UI线程检查表单是否closures,如果没有,则更新文本框。
这不适用于所有人,但是如果你在BackgroundWorker中定期做某事,比如每秒钟或者每隔10秒钟(可能轮询一个服务器),这似乎很有效地停止进程,并且没有错误信息(至less目前为止)并且容易遵循;
public void StopPoll() { MyBackgroundWorker.CancelAsync(); //Cancel background worker AutoResetEvent1.Set(); //Release delay so cancellation occurs soon } private void bw_DoWork(object sender, DoWorkEventArgs e) { while (!MyBackgroundWorker.CancellationPending) { //Do some background stuff MyBackgroundWorker.ReportProgress(0, (object)SomeData); AutoResetEvent1.WaitOne(10000); } }
我将与文本框关联的SynchronizationContext传递给BackgroundWorker,并使用它在UI线程上执行更新。 使用SynchronizationContext.Post,你可以检查控件是处置还是处置。
那我怎么处理呢?
Private Sub BwDownload_RunWorkerCompleted(sender As Object, e As System.ComponentModel.RunWorkerCompletedEventArgs) Handles BwDownload.RunWorkerCompleted If Me.IsHandleCreated Then 'Form is still open, so proceed End If End Sub
一种解决scheme可行,但太复杂。 这个想法是产生计时器,将不断尝试closures窗体,forms将拒绝closures,直到bgWorker
死亡。
private void HandleClosingEvent(object sender, CancelEventArgs e) { if (!this.bgWorker.IsBusy) { // bgWorker is dead, let Closing event proceed. e.Cancel = false; return; } if (!this.bgWorker.CancellationPending) { // it is first call to Closing, cancel the bgWorker. this.bgWorker.CancelAsync(); this.timer1.Enabled = true; } // either this is first attempt to close the form, or bgWorker isn't dead. e.Cancel = true; } private void timer1_Tick(object sender, EventArgs e) { Trace.WriteLine("Trying to close..."); Close(); }
其他方式:
if (backgroundWorker.IsBusy) { backgroundWorker.CancelAsync(); while (backgroundWorker.IsBusy) { Application.DoEvents(); } }