我正在观察我的一些代码中的一个奇怪的错误,我怀疑它与关闭表单和后台工作者交互的方式有关。
以下是可能存在错误的代码:
var worker = new BackgroundWorker();
worker.DoWork += (sender, args) => {
command();
};
worker.RunWorkerCompleted += (sender, args) => {
cleanup();
if (args.Error != null)
MessageBox.Show("...", "...", MessageBoxButtons.OK, MessageBoxIcon.Exclamation);
};
worker.RunWorkerAsync();
当按下按钮时,此代码在表单中的方法中执行。 command()很慢,可能需要几秒钟才能运行。
用户按下执行上述代码的按钮以执行。在完成之前,表单已关闭。
问题是调用cleanup()有时会引发ObjectDisposedException。我说“有时”,因为这在我的电脑上永远不会发生。如果在完成command()之前关闭了表单,则不会执行我为RunWorkerCompleted注册的处理程序。在另一台计算机上,处理程序一次被调用一次。在同事的电脑上,它几乎总是被称为。显然,处理程序的执行概率随着计算机的年龄/速度而上升。
第一个问题:
这是BakgroundWorker的预期行为吗?我不希望它对形式有任何了解,因为我无法看到任何与“工人”形式相关的形式。
第二个问题:
我该如何解决这个问题?
我正在考虑的可能解决方案:
可能相关的其他信息:来自command()的代码将导致对“this”中的GUI对象进行更新。通过调用此F#函数执行此类更新:
/// Run a delegate on a ISynchronizeInvoke (typically a Windows.Form).
let runOnInvoker (notification_receiver : ISynchronizeInvoke) excHandler (dlg : Delegate) args =
try
let args : System.Object[] = args |> Seq.cast |> Array.ofSeq
notification_receiver.Invoke (dlg, args) |> ignore
with
| :? System.InvalidOperationException as op ->
excHandler(op)
答案 0 :(得分:4)
您提到的例外与BackgroundWorker
没有任何关联,除了一个线程(工作者)试图访问已被另一个线程(UI)处置的控件这一事实。
我将使用的解决方案是将事件处理程序附加到Form.FormClosed
事件,以设置一个标志,告诉您UI已被拆除。然后,RunWorkerCompleted
句柄将在尝试对表单执行任何操作之前检查UI是否已被拆除。
虽然如果您没有明确处理表单,这种方法可能比检查IsDisposed
更可靠地工作,但它并不能100%保证表格在清理后不会被关闭和/或处理代码检查了标志,发现它仍然存在。这是你自己提到的竞争条件。
要消除此竞争条件,您需要进行同步,例如:
// set this to new object() in the constructor
public object CloseMonitor { get; private set; }
public bool HasBeenClosed { get; private set; }
private void Form1_FormClosed(object sender, FormClosedEventArgs e) {
lock (this.CloseMonitor) {
this.HasBeenClosed = true;
// other code
}
}
和工人:
worker.RunWorkerCompleted += (sender, args) => {
lock (form.CloseMonitor) {
if (form.HasBeenClosed) {
// maybe special code for this case
}
else {
cleanup();
// and other code
}
}
};
Form.FormClosing
事件也可以正常工作,如果它有所作为,你可以使用两者中哪一个更方便。
请注意,编写此代码的方式,两个事件处理程序都将被安排在UI线程上执行(这是因为WinForms组件使用单线程单元模型),因此您实际上不会受到竞争条件的影响。但是,如果您决定在将来生成更多线程,则可能会暴露竞争条件,除非您使用锁定。在实践中,我经常看到这种情况发生,所以我建议同步,以便面向未来。性能不会受到影响,因为同步只发生一次。