首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >奇怪的InvokeRequired问题

奇怪的InvokeRequired问题
EN

Stack Overflow用户
提问于 2012-07-13 23:24:50
回答 3查看 1.8K关注 0票数 5

我有一个UserControl,上面有一个名为mTreeView的TreeView控件。我可以从多个不同的线程获取数据更新,这些线程会导致TreeView更新。为此,我设计了以下模式:所有数据更新事件处理程序都必须获取锁,然后检查InvokeRequired;如果是这样,则通过调用Invoke来完成这项工作。相关代码如下:

代码语言:javascript
复制
  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是假的,所以这就是为什么它会在启动时发生。不过,他不知道该怎么做。有什么想法吗?

EN

回答 3

Stack Overflow用户

回答已采纳

发布于 2012-07-14 00:06:17

这是一个标准的线程竞赛。在创建TreeView之前,您启动线程的时间太早了。因此,您的代码将InvokeRequired视为false,并在片刻之后创建本机控件时失败。通过仅在窗体的Load事件激发时启动线程来修复此问题,Load事件是保证所有控件句柄都有效的第一个事件。

代码btw中的一些错误概念。使用锁是不必要的,InvokeRequired和Begin/Invoke都是线程安全的。而InvokeRequired是一种反模式。您几乎总是知道该方法将由工作线程调用。因此,只有在InvokeRequired为false时才使用它抛出异常。这将允许及早诊断这个问题。

票数 7
EN

Stack Overflow用户

发布于 2012-07-13 23:49:17

当您重新编组到UI线程时,它是一个线程--它一次只能做一件事。调用Invoke时不需要任何锁。

Invoke的问题在于它阻塞了调用线程。调用线程通常不关心在UI线程上完成了什么get。在这种情况下,我建议使用BeginInvoke将操作异步地封送回UI线程。在某些情况下,后台线程可能会在调用时被阻塞,而UI线程可能会等待后台线程完成某些操作,而您最终会出现死锁:例如:

代码语言:javascript
复制
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来结束一个死锁。

只需执行以下操作:

代码语言:javascript
复制
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方法。

票数 2
EN

Stack Overflow用户

发布于 2012-07-13 23:46:27

你上面展示的模式在我看来是100%好的(尽管有一些额外的不必要的锁定,但是我看不出这会如何导致你所描述的问题)。

正如David W指出的,您正在做的事情和this extension method之间的唯一区别是您直接在UI线程上访问mTreeView,而不是将它作为参数传递给您的操作,然而,只有当mTreeView的值发生变化时,这才会有区别,而且在任何情况下,您都必须非常努力地使其导致您所描述的问题。

这意味着问题一定出在别的地方。

我能想到的唯一一件事是,你可能在UI线程以外的线程上创建了mTreeView -如果是这样的话,访问树视图将是100%安全的,但是如果你尝试将该树视图添加到一个在不同线程上创建的窗体中,那么它将会出现一个类似于你所描述的异常。

票数 1
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/11473496

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档