代码之家  ›  专栏  ›  技术社区  ›  GONeale

不可能的(我多么讨厌用这个词)跨线程错误?

  •  6
  • GONeale  · 技术社区  · 16 年前

    有人能解释一下这怎么可能发生吗?

    我完全知道在编程时考虑到了线程安全,正如你所看到的,我正在通过这里的表单InvokeRequired检查来提供UI更新,一切都很正常,据我所知,没有任何变化会破坏这一点,现在突然间,就在我编程应用程序的其他部分时(可能在某个阶段添加到这个方法中?我只是不记得了),我突然收到了这个错误:

    alt text

    首先,如果InvokeRequired=true,这应该意味着执行了BeginInvoke(),该方法将被[排队]重新调用,并且InvokeRequired应该等于false?

    我不应该接受这个例外,因为它应该得到满足?

    希望听到一些多线程大师的意见:)

    格雷厄姆

    5 回复  |  直到 8 年前
        1
  •  9
  •   Chris Peterson    16 年前

    我怀疑InvokedRrevoked可能在骗你。WinForm控件会推迟创建控件的底层Win32 HWND,直到方法调用真正需要它。如果HWND尚未创建,则InvokeRequired将返回false。

    有关详细说明,请参阅: "Mysterious Hang or The Great Deception of InvokeRequired"

    如果您的后台线程查询InvokeRequired 之前 UI线程导致控件延迟创建其HWND,InvokeRequired将(错误地)告诉您的后台线程它不需要使用Invoke()将控件传递回UI线程。当后台线程访问控件时,BAM! “InvalidOperationException:跨线程操作无效!”

    UI线程可以手动强制控件为其创建HWND句柄。InvokeRequired将知道UI线程是控件的所有者:

    Control control = new Control();
    IntPtr handle = control.Handle; // if you ask for HWND, it will be created
    
        2
  •  5
  •   Jonathan C Dickinson    16 年前

    大多数人看到这个错误后都会看到一件事,“你没有从主UI线程访问这个控件。”实际上,如果你愿意,你可以有100个UI线程(这个行为没有定义,但受支持)。panelMain很可能是在与(this)不同的线程上创建的;我看不到代码,但看起来你是在worker/线程中创建它的。

    要确认该行为,请尝试以下操作:

    Action addAction = new Action (
       new delegate { panelMain.Controls.Add(UCLoadingScreen); } )
    if(panelMain.InvokeRequired)
    {
       panelMain.Invoke(addAction); // Breakpoint here.
    }
    else
    {
       addAction();
    }
    

    为不同的错误做好准备(与父线程不同的线程上的子控件,不确定会出现什么错误,但我非常确定会出现)。这不是一个解决方案。

    然而,工厂会解决这个问题:

    public void CreateControl<T>() where T : new()
    {
        if(InvokeRequired)
        {
            return (T)Invoke(new Func<T>(CreateControl<T>()));
        }
        return new T();
    }
    

    编辑:panelMain可能不是线程的“违规者”,正如我所说,来自不同线程的养育控制会导致高度未定义的行为。确保所有控件都是在主窗体线程的上下文中创建的。

        3
  •  0
  •   Mike Weller    16 年前

    没有不可能的跨线程错误!

        4
  •  0
  •   GregC Benjamin Baumann    16 年前

    我希望这对你有用。 我想你的gui东西在一个线程上。只需初始化此单例,并在您想调用Control时依赖它。调用必需属性。

    干杯,

    -格雷格

    public sealed class UiThread
    {
      #region Singleton
      // Singleton pattern implementation taken from Jon Skeet's C# and .NET article www.yoda.arachsys.com/csharp/singleton.html
    
      UiThread() { }
    
      public static UiThread Instance { get { return Nested.instance; } }
    
      class Nested
      {
         // Explicit static constructor to tell C# compiler
         // not to mark type as beforefieldinit
         static Nested() { }
    
         internal static readonly UiThread instance = new UiThread();
      }
      #endregion
    
      int _uiThreadId = 0;
    
      public void SetUiThread(Thread thread)
      {
         if (_uiThreadId != 0)
            throw new ApplicationException("UI Thread has already been established!");
    
         if (thread.ManagedThreadId == 0)
            throw new ArgumentException("Unexpected thread id value of 0!");
    
         if (thread.IsBackground)
            throw new ArgumentException("Supplied thread should not be a background thread!");
    
         if (thread.IsThreadPoolThread)
            throw new ArgumentException("Supplied thread should not be a thread pool thread!");
    
         _uiThreadId = thread.ManagedThreadId;
      }
    
      /// <summary>
      /// It's possible for InvokeRequired to return false when running in background thread.
      /// This happens when unmanaged control handle has not yet been created.
      /// We second-guess Microsoft's implementation in this case, checking against foreground thread's Id.
      /// </summary>
      /// <param name="control">Control to check against.</param>
      public bool InvokeRequired(Control control)
      {
         if (control.InvokeRequired)
            return true;
    
         IntPtr unmanagedHandle = control.Handle;
         bool newResult = control.InvokeRequired;
    
         if (unmanagedHandle == IntPtr.Zero)
         {
            // Trace.WriteLine() call here forces unmanagedHandle's initialization, 
            //  even with optimizing compiler.
            Trace.WriteLine(string.Format("Control handle could not be established! Control was {0}.", control.ToString()));
         }
    
         bool retVal = InvokeRequired();
    
         // Break whenever the result of our check does not match theirs.
         Debug.Assert(retVal == newResult);
    
         // Return our InvokeRequired result over theirs 
         //   to keep with the tradition of updating controls from foreground only.
         return retVal;
      }
    
      /// <summary>
      /// Prefer to use overload with Control argument if at all possible.
      /// It's possible for InvokeRequired to return false when running in background thread.
      /// This happens when unmanaged control handle has not yet been created.
      /// We second-guess Microsoft's implementation in this case, checking against foreground thread's Id.
      /// </summary>
      public bool InvokeRequired()
      {
         if (_uiThreadId == 0)
            throw new ApplicationException("UI Thread has not been established!");
    
         return (Thread.CurrentThread.ManagedThreadId != _uiThreadId);
      }
    

    }

        5
  •  -1
  •   bleevo    16 年前

    使用

    if (InvokeRequired)
    {
      //invoke
    }
    else
    {
      //do
    }
    
    推荐文章