如何停止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中完成)。 我没有删除这条线,虽然它似乎没有什么区别。

我添加了一个将我的全局variablesbClos​​ingForm设置为True的行。 然后,我在BackgroundWorker_WorkCompleted中添加了一行代码,在执行任何结束步骤之前,检查e.Cancelled以及全局variablesbClos​​ingForm。

使用这个模板,即使背景工作者处于某个事物的中间,这个模板也可以随时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(); } }