我继承了一些代码,它有两个非UI线程来更新各种WinForm控件 代码使用InvokeRequired和Invoke来更新UI;但是,我仍然偶尔得到错误:跨线程操作无效:控制'lvReports'访问除了它之外的其他线程。
我怀疑我正在处理一个竞争条件,我需要在下面的方法中引入一个锁,但是说,我可以找到几十个关于如何安全地从非UI线程更新UI但没有示例的示例或讨论如何处理在竞赛场景中更新相同控件的两个线程。
所以我的问题是:我如何重写下面的代码,以便在给定竞争条件的情况下正确地更新UI,并且我需要从非UI线程更新UI?
// two separate theads call this method in a instance of a WinForm
private void LoadReports()
{
if (this.InvokeRequired)
{
this.Invoke(new MethodInvoker(this.LoadReports));
}
else
{
// some code removed to keep exampe simple...
SetCtlVisible(lvReports, true);
if (this.InvokeRequired)
{
this.Invoke((MethodInvoker)delegate { lvReports.Refresh(); });
}
else
{
lvReports.Refresh();
}
}
}
delegate void SetVisibleCallback(Control ctl, bool visible);
private void SetCtlVisible(Control ctl, bool visible)
{
if (ctl.InvokeRequired)
{
SetVisibleCallback d = new SetVisibleCallback(SetCtlVisible);
ctl.Invoke(d, new object[] { ctl, visible });
}
else
{
ctl.Visible = visible;
}
}
以下是一些想法: this.InvokeRequired是否随时与ctl.InvokeRequired不同? 给出第一个需要的第二个InvokeRequired测试吗? 如果我保留第一个InvokeRequired,是否需要实现SetCtlVisible? 我应该删除第一个InvokeRequired并保留else子句中的所有代码吗? 是否需要在else子句周围锁定?
答案 0 :(得分:9)
像这样使用InvokeRequired是一种反模式。您知道从线程调用此方法,InvokeRequired应始终为true。
现在您可以使用它来解决您的问题。如果它是假的那么就有严重的错误。抛出异常,调试器将停止并让您找出它无法正常工作的原因。并且总是调用Invoke(),调用一个辅助方法来完成其余的LoadReports()。
另请注意,您在其余代码中使用它时出错了。您知道 LoadReports()的其余部分在UI线程上运行,您使用了Invoke()。没有必要再次测试它,包括在SetCtlVisible()内部。
获取炸弹的典型原因是因为线程在创建窗体窗口之前过早运行LoadReports()。你需要把它联系起来。表单的Load事件就是信号。