我有一个UserControl,上面有一个名为mTreeView的TreeView控件。我可以从多个不同的线程获取数据更新,这些更新导致TreeView被更新。为此,我设计了以下模式:
所有数据更新事件处理程序必须获取一个锁,然后检查InvokeRequired;如果是这样,请通过调用Invoke来完成工作。以下是相关代码:
public partial class TreeViewControl : UserControl
{
object mLock = new object();
void LockAndInvoke(Control c, Action a)
{
lock (mLock)
{
if (c.InvokeRequired)
{
c.Invoke(a);
}
else
{
a();
}
}
}
public void DataChanged(object sender, NewDataEventArgs e)
{
LockAndInvoke(mTreeView, () =>
{
// get the data
mTreeView.BeginUpdate();
// perform update
mTreeView.EndUpdate();
});
}
}
我的问题是,有时,在启动时,我会在mTreeView.BeginUpdate()上收到InvalidOperationException,说正在从与创建线程不同的线程更新mTreeView。我返回到我的LockAndInvoke的调用堆栈中,瞧瞧,c.InvokeRequired为true,但是else分支被占用了!就像在采用else分支之后,在另一个线程上将InvokeRequired设置为true一样。
我的方法有什么问题,我该怎么做才能防止这种情况发生?
编辑:我的同事告诉我,问题是在创建控件之前InvokeRequired为假,因此这就是启动时发生的原因。他不确定该怎么办。有任何想法吗?
最佳答案
这是一个标准的线程竞赛。在创建TreeView之前,您启动线程的时间太早。因此,您的代码将InvokeRequired视为false,并在稍后创建 native 控件时失败。通过仅在窗体的Load事件触发时启动线程来解决此问题,该事件是第一个保证所有控件句柄均有效的事件。
代码中有一些误解。不需要使用锁,InvokeRequired和Begin/Invoke都是线程安全的。 InvokeRequired是一种反模式。您几乎总是知道该方法将由工作线程调用。因此,仅在InvokeRequired为false时才使用它引发异常。这样可以及早诊断此问题。