gpt4 book ai didi

c# - 使用后台 worker 时如何正确处理表单关闭?

转载 作者:行者123 更新时间:2023-11-30 15:46:22 25 4
gpt4 key购买 nike

我在我的一些代码中观察到一个奇怪的错误,我怀疑它与关闭表单和后台工作人员交互的方式有关。

这里是可能有问题的代码:

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 的预期行为吗?我不希望它知道有关表单的任何信息,因为我看不到任何将表单“this”与“worker”联系起来的东西。

第二个问题:

我应该如何解决这个问题?

我正在考虑的可能解决方案:

  1. 在调用 cleanup() 之前测试是否 (!this.IsDisposed)。这够了吗,还是可以在执行清理时处理掉表单?
  2. 将对 cleanup() 的调用包装在 try {} catch (ObjectDisposedException) 中。我不太喜欢这种方法,因为我可能会捕获由于 cleanup() 或它调用的方法之一中的其他不相关错误而引发的异常。
  3. 注册 IsClosing 的处理程序并延迟或取消关闭,直到 RunWorker Completed 的处理程序运行。

可能相关的其他信息:来自 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)

最佳答案

您提到的异常与 BackgroundWorker 没有任何联系,除了一个线程(worker)试图访问已被另一个线程(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 :

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 组件使用单线程单元模型),因此您实际上不会受到竞争条件的影响.但是,如果您决定在将来生成更多线程,您可能会暴露竞争条件,除非您确实使用锁定。在实践中,我经常看到这种情况发生,所以我建议无论如何都要进行同步,以防万一。性能不会受到影响,因为同步只会发生一次。

关于c# - 使用后台 worker 时如何正确处理表单关闭?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/4387527/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com