如何在表单的关闭事件上停止BackgroundWorker?
我有一个生成BackgroundWorker的表单,它应该更新表单自己的文本框(在主线程上),因此 Invoke((Action) (...));
调用。
如果在 HandleClosingEvent
中我只是执行 bgWorker.CancelAsync()
,那么我会在 Invoke(...)
上得到 ObjectDisposeException
打电话,可以理解。但是,如果我坐在 HandleClosingEvent
中等待 bgWorker 完成,那么 .Invoke(...) 永远不会返回,这也是可以理解的。
有什么想法如何关闭这个应用程序而不会出现异常或死锁?
以下是简单 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
}
I have a form that spawns a BackgroundWorker, that should update form's own textbox (on main thread), hence Invoke((Action) (...));
call.
If in HandleClosingEvent
I just do bgWorker.CancelAsync()
then I get ObjectDisposedException
on Invoke(...)
call, understandably. But if I sit in HandleClosingEvent
and wait for bgWorker to be done, than .Invoke(...) never returns, also understandably.
Any ideas how do I close this app without getting the exception, or the deadlock?
Following are 3 relevant methods of the simple Form1 class:
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
}
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(12)
据我所知,执行此操作的唯一死锁安全和异常安全方法是实际取消 FormClosing 事件。如果 BGW 仍在运行,则设置 e.Cancel = true 并设置一个标志以指示用户请求关闭。然后检查 BGW 的 RunWorkerCompleted 事件处理程序中的该标志,如果已设置,则调用 Close()。
The only deadlock-safe and exception-safe way to do this that I know is to actually cancel the FormClosing event. Set e.Cancel = true if the BGW is still running and set a flag to indicate that the user requested a close. Then check that flag in the BGW's RunWorkerCompleted event handler and call Close() if it is set.
我找到了另一种方法。如果您有更多的backgroundWorkers,您可以制作:
并在每个backgroundWorker的DoWork方法中制作:
您可以使用的Arter:
我在Control中的Word插件中使用了它,我在
CustomTaskPane
中使用了它。如果有人提前关闭文档或应用程序,然后我的所有后台工作都完成了工作,则会引发一些COM 异常
(我不记得具体是哪个)。CancelAsync()
不会。不工作。但是有了这个,我可以在
DocumentBeforeClose
事件中立即关闭backgroundworkers
使用的所有线程,我的问题就解决了。I've found another way. If you have more backgroundWorkers you can make:
and in every backgroundWorker's DoWork method make:
Arter that you can use:
I used this in Word Add-in in Control, which i use in
CustomTaskPane
. If someone close the document or application earlier then all my backgroundWorkes finishes their work, it raises someCOM Exception
(I don't remember exatly which).CancelAsync()
doesn't work.But with this, I can close all threads which are used by
backgroundworkers
Immediately inDocumentBeforeClose
event and my problem is solved.这是我的解决方案(抱歉,它是在 VB.Net 中)。
当我运行 FormClosing 事件时,我运行 BackgroundWorker1.CancelAsync() 将 CancellationPending 值设置为 True。不幸的是,程序从未真正有机会检查 CancellationPending 值以将 e.Cancel 设置为 true (据我所知,这只能在 BackgroundWorker1_DoWork 中完成)。
我没有删除该行,尽管它似乎并没有真正产生影响。
我添加了一行将全局变量 bClosingForm 设置为 True。然后,我在 BackgroundWorker_WorkCompleted 中添加了一行代码,以在执行任何结束步骤之前检查 e.Cancelled 以及全局变量 bClosingForm。
使用此模板,您应该能够随时关闭表单,即使后台工作人员正在处理某些事情(这可能不太好,但它肯定会发生,因此最好对其进行处理)。我不确定是否有必要,但在这一切发生后,您可以在 Form_Closed 事件中完全处置后台工作人员。
Here was my solution (Sorry it's in VB.Net).
When I run the FormClosing event I run BackgroundWorker1.CancelAsync() to set the CancellationPending value to True. Unfortunately, the program never really gets a chance to check the value CancellationPending value to set e.Cancel to true (which as far as I can tell, can only be done in BackgroundWorker1_DoWork).
I didn't remove that line, although it doesn't really seem to make a difference.
I added a line that would set my global variable, bClosingForm, to True. Then I added a line of code in my BackgroundWorker_WorkCompleted to check both e.Cancelled as well as the global variable, bClosingForm, before performing any ending steps.
Using this template, you should be able to close your form out at any time even if the backgroundworker is in the middle of something (which might not be good, but it's bound to happen so it might as well be dealt with). I'm not sure if it's necessary, but you could dispose the Background worker entirely in the Form_Closed event after this all takes place.
你不能在表单的析构函数中等待信号吗?
Can you not wait on the signal in the destructor of the form?
首先,ObjectDisposeException 只是这里可能的一个陷阱。运行OP的代码在很多情况下都会产生以下InvalidOperationException:
我想这可以通过在“已加载”回调上启动工作程序而不是在构造函数上来修改,但是如果使用BackgroundWorker的进度报告机制,则可以完全避免整个痛苦。以下效果很好:
我劫持了百分比参数,但可以使用其他重载来传递任何参数。
有趣的是,删除上述睡眠调用会阻塞 UI、消耗大量 CPU 并不断增加内存使用量。我猜这与GUI的消息队列过载有关。然而,在 sleep 调用完好无损的情况下,CPU 使用率几乎为 0,内存使用率似乎也很好。为了谨慎起见,也许应该使用比 1 ms 更高的值?如果有专家意见,我们将不胜感激...更新:看来只要更新不是太频繁,就应该没问题:链接
无论如何,我无法预见必须更新 GUI 的情况间隔短于几毫秒(至少,在人类观看 GUI 的情况下),所以我认为大多数时候进度报告是正确的选择
Firstly, the ObjectDisposedException is only one possible pitfall here. Running the OP's code has produced the following InvalidOperationException on a substantial number of occasions:
I suppose this could be amended by starting the worker on the 'Loaded' callback rather than the constructor, but this entire ordeal can be avoided altogether if BackgroundWorker's Progress reporting mechanism is used. The following works well:
I kind of hijacked the percentage parameter but one can use the other overload to pass any parameter.
It is interesting to note that removing the above sleep call clogs the UI, consumes high CPU and continually increases the memory use. I guess it has something to do with the message queue of the GUI being overloaded. However, with the sleep call intact, the CPU usage is virtually 0 and the memory usage seems fine, too. To be prudent, perhaps a higher value than 1 ms should be used? An expert opinion here would be appreciated... Update: It appears that as long as the update isn't too frequent, it should be OK: Link
In any case, I can't foresee a scenario where the updating of the GUI has to be in intervals shorter than a couple of milliseconds (at least, in scenarios where a human is watching the GUI), so I think most of the time progress reporting would be the right choice
我真的不明白为什么在这种情况下,如果您使用 this.enabled = false,DoEvents 被认为是一个糟糕的选择。我认为这会让它变得非常整洁。
I really dont see why DoEvents is regarded as such a bad choice in this case if you are using this.enabled = false. I think it would make it quite neat.
您的后台工作者不应使用 Invoke 来更新文本框。它应该很好地要求 UI 线程使用事件 ProgressChanged 以及要放入附加文本框中的值来更新文本框。
在事件 Closed(或者可能是事件 Closing)期间,UI 线程会在取消后台工作程序之前记住表单已关闭。
收到 ProgressChanged 后,UI 线程检查表单是否已关闭,只有在没有关闭的情况下,才会更新文本框。
Your backgroundworker should not use Invoke to update the textbox. It should ask the UI thread nicely to update the textbox using event ProgressChanged with the value to put in the textbox attached.
During event Closed (or maybe event Closing), the UI thread remembers that the form is closed before it cancels the backgroundworker.
Upon receiving the progressChanged the UI thread checks if the form is closed and only if not, it updates the textbox.
这并不适合所有人,但是如果您定期在后台工作程序中执行某些操作,例如每秒或每 10 秒(可能轮询服务器),这似乎可以很好地以有序的方式停止该进程,并且不会出现错误消息(至少到目前为止)并且易于理解;
This won't work for everyone, but if you are doing something in a BackgroundWorker periodically, like every second or every 10 seconds, (perhaps polling a server) this seems to work well to stop the process in an orderly manner and without error messages (at least so far) and is easy to follow;
我会将与文本框关联的 SynchronizationContext 传递给 BackgroundWorker,并使用它在 UI 线程上执行更新。使用SynchronizationContext.Post,您可以检查控件是否已释放或已释放。
I'd pass in the SynchronizationContext associated with the textbox to the BackgroundWorker and use that to perform Updates on the UI thread. Using SynchronizationContext.Post, you can check if the control is disposed or disposing.
Me.IsHandleCreated 怎么样?
What about Me.IsHandleCreated?
另一种方式:
Another way:
一种有效的解决方案,但过于复杂。这个想法是生成一个计时器,该计时器将继续尝试关闭表单,并且表单将拒绝关闭,直到所述 bgWorker 死亡。
One solution that works, but too complicated. The idea is to spawn the timer that will keep trying to close the form, and form will refuse to close until said
bgWorker
is dead.