我有一个带有TreeView控件的UserControl,名为mTreeView。我可以从多个不同的线程获取数据更新,这些更新会导致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是真的但是其他分支已被占用!就像在调用else分支后,在另一个线程上将InvokeRequired设置为true一样。
我的方法有什么问题,我该怎么做才能防止这种情况发生?
编辑:我的同事告诉我,问题是InvokeRequired在创建控件之前是假的,所以这就是它在启动时发生的原因。但他不知道该怎么办。有什么想法吗?答案 0 :(得分:7)
这是标准的穿线比赛。在创建TreeView之前,您将很快启动该线程。因此,您的代码将InvokeRequired视为false,并在创建本机控件后一瞬间失败。通过仅在表单的Load事件触发时启动线程来修复此问题,第一个事件保证所有控件句柄都有效。
代码中的一些误解btw。不需要使用 lock ,InvokeRequired和Begin / Invoke都是线程安全的。而InvokeRequired是一种反模式。您几乎总是知道该方法将由工作线程调用。因此,只使用InvokeRequired在异常时抛出异常。哪个可以尽早诊断出这个问题。
答案 1 :(得分:1)
上面显示的模式对我来说看起来很好(虽然有一些额外的不必要的锁定,但是我无法看到这会导致你所描述的问题)。
正如David W指出的那样,你正在做的事情和this extension method之间的唯一区别是你直接访问UI线程上的mTreeView
而不是将其作为参数传递给你的行为,但是如果mTreeView
的值发生变化,这只会产生影响,而且无论如何你都必须努力尝试才能解决问题。
这意味着问题必须是其他问题。
我唯一能想到的是你可能在UI线程以外的线程上创建了mTreeView
- 如果是这种情况,那么访问树视图将是100%安全的,但是如果你尝试将该树视图添加到在不同线程上创建的表单,然后它会发出类似于您描述的异常的异常。
答案 2 :(得分:1)
当你编组回UI线程时,它是一个线程 - 它一次只能做一件事。调用Invoke时不需要任何锁定。
Invoke的问题是它阻塞了调用线程。调用线程通常不关心在UI线程上完成什么。在这种情况下,我建议使用BeginInvoke将操作异步编组回UI线程。在某些情况下,可以在Invoke上阻止后台线程,而UI线程可以等待后台线程完成某些操作并最终导致死锁:例如:
private bool b;
public void EventHandler(object sender, EventArgs e)
{
while(b) Thread.Sleep(1); // give up time to any other waiting threads
if(InvokeRequired)
{
b = true;
Invoke((MethodInvoker)(()=>EventHandler(sender, e)), null);
b = false;
}
}
...上面的内容会在while循环中死锁,因为在调用EventHandler返回之前Invoke不会返回,并且在b为false之前EventHandler不会返回...
请注意我使用bool来阻止某些代码段运行。这与锁非常相似。所以,是的,你可以通过使用锁来结束僵局。
只需这样做:
public void DataChanged(object sender, NewDataEventArgs e)
{
if(InvokeRequired)
{
BeginInvoke((MethodInvoker)(()=>DataChanged(sender, e)), null);
return;
}
// get the data
mTreeView.BeginUpdate();
// perform update
mTreeView.EndUpdate();
}
这只是在UI线程上异步重新调用DataChanged方法。