应该在访问UI控件时始终使用Control.InvokeRequired

时间:2012-08-20 18:02:25

标签: c# .net winforms clr ui-thread

我们正在使用WinForms(3.5)构建.NET应用程序。

我最近添加了一项新功能,并在访问某些控件时开始遇到奇怪的行为。问题是某些UI控件访问只是暂停了执行(没有看到异常)。

仔细检查(使用WinDbg)我意识到控件是从ThreadPool线程更新的,并且抛出了CrossThreadMessagingException。

我的问题是 - 有关如何规避此类行为的良好做法吗?

这将非常麻烦,但也许不可能围绕使用Control.Invoke方法访问UI控件的每个代码位置。

如何将我的代码划分为不应该使用Invoke的“安全”代码,应该从哪个代码?

1 个答案:

答案 0 :(得分:9)

如果应用程序设计为多线程,则可以发生交叉线程,因此您需要使用InvokeRequired检查它,并且要么使用您的方法在UI线程上调用re-Invoke()本身,或者抛出一个异常,指示代码被不正确地使用。请记住,InvokeRequired在某些情况下将是假的(主要是当窗口没有句柄或正在处理时);防止这些情况的最好方法是不要在窗口初始化过程中比Load()事件处理程序更早地启动线程,并通过取消窗口创建的后台线程并等待它们关闭来处理Closing()事件。 / p>

如果应用程序不是多线程的(您没有设置BackgroundWorkers,TPL操作,BeginInvoke()代理或Start()线程),那么就没有必要了。但是,对InvokeRequired的调用非常便宜(其背后的逻辑基本上是检查WinAPI函数GetThreadId和GetWindowThreadProcessId返回相同的值),因此如果您预期重构的程序是多线程的,则调用方法的以下模式很简单足以实现:

//no return value, no parameters; ShowWindow(), HideWindow(), etc
//Understand that many built-in control methods are not virtual and so you can't 
//override them to do this; you must either hide them or ensure the caller is
//checking for cross-threading.
public void MyWindowMethod()
{
   if(InvokeRequired)
      this.Invoke(new Action(MyWindowMethod));
   else
   {
      //main logic
   }
}

//Input but no return; SetTitle("My Title")
public void MyWindowMethod2(string input)
{
   if(InvokeRequired)
      this.Invoke(new Action<string>(MyWindowMethod2), input);
   else
   {
      //main logic
   }
}

//inputs and outputs; custom methods, advanced graphics
public string MyWindowMethod3(string input)
{
   if(InvokeRequired)
      return (string)(this.Invoke(new Func<string, string>(MyWindowMethod3), input));

   //No else required; the return makes it redundant
   //main logic   
}