我有一个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是真的,但是else分支被采用了!这就像是在执行else分支之后,在另一个线程上将InvokeRequired设置为true。
我的方法有什么问题吗?我能做些什么来防止这种情况发生?
编辑:我的同事告诉我,问题是在创建控件之前,InvokeRequired是假的,所以这就是为什么它会在启动时发生。不过,他不知道该怎么做。有什么想法吗?
发布于 2012-07-14 00:06:17
这是一个标准的线程竞赛。在创建TreeView之前,您启动线程的时间太早了。因此,您的代码将InvokeRequired视为false,并在片刻之后创建本机控件时失败。通过仅在窗体的Load事件激发时启动线程来修复此问题,Load事件是保证所有控件句柄都有效的第一个事件。
代码btw中的一些错误概念。使用锁是不必要的,InvokeRequired和Begin/Invoke都是线程安全的。而InvokeRequired是一种反模式。您几乎总是知道该方法将由工作线程调用。因此,只有在InvokeRequired为false时才使用它抛出异常。这将允许及早诊断这个问题。
发布于 2012-07-13 23:49:17
当您重新编组到UI线程时,它是一个线程--它一次只能做一件事。调用Invoke时不需要任何锁。
Invoke的问题在于它阻塞了调用线程。调用线程通常不关心在UI线程上完成了什么get。在这种情况下,我建议使用BeginInvoke将操作异步地封送回UI线程。在某些情况下,后台线程可能会在调用时被阻塞,而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循环中死锁,因为Invoke直到对EventHandler的调用返回后才会返回,而EventHandler直到b为false才会返回...
请注意,我使用bool来停止某些代码段的运行。这与lock非常相似。所以,是的,你可以通过使用lock来结束一个死锁。
只需执行以下操作:
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方法。
发布于 2012-07-13 23:46:27
你上面展示的模式在我看来是100%好的(尽管有一些额外的不必要的锁定,但是我看不出这会如何导致你所描述的问题)。
正如David W指出的,您正在做的事情和this extension method之间的唯一区别是您直接在UI线程上访问mTreeView,而不是将它作为参数传递给您的操作,然而,只有当mTreeView的值发生变化时,这才会有区别,而且在任何情况下,您都必须非常努力地使其导致您所描述的问题。
这意味着问题一定出在别的地方。
我能想到的唯一一件事是,你可能在UI线程以外的线程上创建了mTreeView -如果是这样的话,访问树视图将是100%安全的,但是如果你尝试将该树视图添加到一个在不同线程上创建的窗体中,那么它将会出现一个类似于你所描述的异常。
https://stackoverflow.com/questions/11473496
复制相似问题