C# 访问UI控件时是否应始终使用Control.InvokeRequest

C# 访问UI控件时是否应始终使用Control.InvokeRequest,c#,.net,winforms,clr,ui-thread,C#,.net,Winforms,Clr,Ui Thread,我们正在使用WinForms(3.5)构建一个.NET应用程序 我最近添加了一个新功能,在访问某些控件时开始出现奇怪的行为。问题是一些UI控件访问只是停止了执行(没有看到异常) 在仔细检查下(使用WinDbg),我意识到控件是从线程池线程更新的,并且抛出了CrossThreadMessagingException 我的问题是——在如何规避这种行为方面有什么好的做法吗 用Control.Invoke方法包围访问UI控件的每个代码位置可能会非常麻烦,但也不可能 如何将我的代码从应该使用Invoke的

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

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

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

我的问题是——在如何规避这种行为方面有什么好的做法吗

用Control.Invoke方法包围访问UI控件的每个代码位置可能会非常麻烦,但也不可能

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

如果应用程序设计为多线程,则可能会发生交叉线程,因此您需要使用InvokeRequired检查它,或者在UI线程上使用您正在调用的方法re-Invoke(),或引发异常,该异常将指示代码使用不当。请记住,invokererequired在某些情况下(主要是当窗口没有句柄或正在/已经处理时)将为false;防止这些情况的最佳方法是,在窗口初始化过程中,不要在Load()事件处理程序之前启动线程,并通过取消窗口创建的后台线程并等待它们关闭来处理Closing()事件

如果应用程序不是多线程的(您没有设置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   
}
//无返回值,无参数;显示窗口()、隐藏窗口()等
//了解许多内置控制方法不是虚拟的,因此您不能
//推翻他们这样做;您必须隐藏它们或确保调用方是
//检查交叉线程。
公共void MyWindowMethod()
{
如果(需要调用)
调用(新操作(MyWindowMethod));
其他的
{
//主要逻辑
}
}
//输入但不返回;片名(“我的片名”)
公共void MyWindowMethod2(字符串输入)
{
如果(需要调用)
调用(新操作(MyWindowMethod2),输入);
其他的
{
//主要逻辑
}
}
//投入和产出;自定义方法,高级图形
公共字符串MyWindowMethod3(字符串输入)
{
如果(需要调用)
return(string)(this.Invoke(newfunc(MyWindowMethod3),input));
//没有其他要求;返回使其成为冗余
//主要逻辑
}

Safe
代码在UI线程上运行。所有其他涉及UI的代码都必须使用
调用
路由。我并不总是控制代码运行的位置。。。一些代码可能会从线程池线程上运行的方法中调用,等等。这就是为什么我要寻找一种模式或最佳实践来避免这些场景(而不是在测试过程中意外地找到它们)。那些“可能会从线程池调用”的代码不应该访问GUI。当它(很少)真的需要的时候,用invokererequired围绕它。是的,你需要停止从非UI线程调用UI方法。你听起来好像在否认这一点。你说“没有发现异常”:这可能与Windows的不当行为有关