环顾四周,我想出了这个:
// UPDATE DISPLAY items (using Invoke in case running on BW thread).
IAsyncResult h = BeginInvoke((MethodInvoker)delegate
{
FooButton.Text = temp1;
BarUpdown.Value = temp2;
}
);
EndInvoke(h); // Wait for invoke to complete.
h.AsyncWaitHandle.Close(); // Explicitly close the wait handle.
// (Keeps handle count from growing until GC.)
详情:
- 我完全删除了
if (InvokeRequired)。 (从 Peter Duniho 的回答中发现。) Invoke() 在 UI 线程上工作得很好。在仅在 UI 线程上运行的代码中,UI 操作不需要特殊处理。在仅在非 UI 线程上运行的代码中,将所有 UI 操作包装在 Invoke() 中。在可以在 UI 线程或非 UI 线程上运行的代码中,同样将所有 UI 操作包装在 Invoke() 中。在 UI 线程上运行时始终使用 Invoke() 会增加一些开销,但是:开销不大(我希望);无论如何,这些操作在 UI 线程上运行的频率较低;并且始终使用 Invoke,您不必为 UI 操作编写两次代码。我被卖了。
- 我将
Invoke() 替换为BeginInvoke() .. EndInvoke() .. AsyncWaitHandle.Close()。 (找到elsewhere。)Invoke() 可能只是BeginInvoke() .. EndInvoke(),所以这只是内联扩展(目标代码稍多;执行速度稍快)。添加AsyncWaitHandle.Close() 可以解决其他问题:在非UI 线程上运行时,Invoke() 会留下数百个句柄,这些句柄在垃圾回收之前一直存在。 (在任务管理器中看到句柄数量增加是很可怕的。)使用BeginInvoke() .. EndInvoke() 使挥之不去的句柄保持不变。 (惊喜:仅使用BeginInvoke() 不会留下句柄;看起来EndInvoke() 是罪魁祸首。)使用AsyncWaitHandle.Close() 显式杀死死句柄消除了挥之不去的句柄的[化妆品] 问题。在 UI 线程上运行时,BeginInvoke() .. EndInvoke()(如Invoke())没有句柄,因此AsyncWaitHandle.Close() 是不必要的,但我认为它也不会花费太多。
- IsDisposed 测试在竞争条件下似乎是安全的,但我认为没有必要。我担心 BackgroundWorker 可以 Invoke() 操作;当它处于挂起状态时,单击可以触发 UI 线程上的回调,该回调可以 Close() 表单,然后消息循环执行此操作。 (不确定这是否会发生。)
问题:(我会在这里更新,当某些东西有效时。)我将我所有的 UI 更新从在 UI 计时器 kludge 上运行更改为使用 Invoke() (如上),现在关闭表单在大约 20 的竞争条件下失败% 的时间。如果用户单击停止我的后台工作人员,则在此之后单击关闭可以正常工作。但是,如果用户直接单击关闭,则会触发 UI 线程上的回调,该回调关闭()表单;触发另一个标记后台工作人员停止的;后台工作人员继续,它在 EndInvoke() 处崩溃,说“无法访问已处置的对象。对象名称:'MainWin'。在 System.Windows.Forms.Control.MarshaledInvoke(Control caller, Delegate method, Object[] args, Boolean同步)...”。在EndInvoke() .. AsyncWaitHandle.Close() 周围添加if (!this.IsDisposed) {} 并不能解决问题。
选项:返回使用表单计时器:让 BW 将其更改写入十几个全局“邮箱”变量。让计时器执行FooButton.Text = nextFooButtonText; 等。大多数此类分配几乎什么都不做,因为设置表单字段只会在值实际更改时更新显示。 (为了清晰和减少复制对象,将邮箱变量初始化为null,并让计时器做if (nextFooButtonText != null) { FooButton.Text = nextFooButtonText; nextFooButtonText = null; }等。)计时器每隔这么多毫秒就会在UI消息循环上放置一个新事件,这比磨更傻Invoke()s。在计时器回调上更新显示会使每次更新延迟 [最多] 计时器间隔。 (呸。)
工作选项:仅使用BeginInvoke()。为什么要让 BW 等待每个 Invoke 完成? 1) temp1 和 temp2 似乎作为引用传递 - 如果它们在 BeginInvoke() 之后发生更改,则新值获胜。 (但这还不错。) 2) temp1 和 temp2 可能超出范围。 (但是在最后一个引用消失之前,它们不安全不会被释放吗?) 3) 等待确保 BW 一次只有一个调用的操作挂起 - 如果 UI 线程阻塞一段时间,BW 无法将其埋入事件。 (但我的 UI 线程不能阻塞,至少在我的 BW 运行时不会阻塞。)
选项:将try .. catch 放在EndInvoke() 周围。 (呸。)
我看到了其他几个建议的技巧:
• 让Close 自行取消,启动计时器,然后返回,以便在UI 线程上完成任何延迟的Invoke();不久之后,计时器回调执行了真正的关闭(找到 here;来自 here)。
•杀死后台工作线程。
•更改 Program.cs 以不同方式关闭。