应该控制。在访问UI控件时总是使用InvokeRequired

时间:2022-04-04 15:55:22

We are building a .NET application using WinForms (3.5).

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

I have added a new feature recently, and started experiencing weird behavior when accessing certain controls. The problem was that some UI control access simply halted the execution (no exception was seen).

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

Under close examination (using WinDbg) I realized the controls were being updated from a ThreadPool thread, and a CrossThreadMessagingException was thrown.

在仔细检查(使用WinDbg)之后,我意识到控件正在从一个ThreadPool线程更新,并抛出了一个CrossThreadMessagingException。

My question is -- is there any good practice on how to circumvent such behavior?

我的问题是——关于如何规避这种行为有什么好的做法吗?

It would be very cumbersome but also perhaps not possible to surround every code location that accesses UI controls with the Control.Invoke method.

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

How can i partition my code to "safe" code that shouldn't use Invoke, from one that should?

如何将代码划分为不应该使用调用的“安全”代码和应该使用调用的代码?

1 个解决方案

#1


8  

If the application is designed to be multithreaded, then cross-threading can occur and thus you need to check for it using InvokeRequired, and either have the method you are calling re-Invoke() itself on the UI thread, or throw an exception that will indicate code is being used improperly. Keep in mind that InvokeRequired will be false in certain circumstances (mainly when the window doesn't have a handle or is being/has been disposed); the best way to prevent these circumstances is to not start threads earlier in the window initialization process than the Load() event handler, and to handle the Closing() event by cancelling background threads created by the window and waiting for them to close.

如果应用程序被设计为多线程的,那么可能会发生交叉线程,因此需要使用InvokeRequired检查它,并在UI线程上使用正在调用的方法reinvoke()本身,或者抛出异常,表明代码使用不当。记住,invokerequerred在某些情况下是假的(主要是当窗口没有句柄或正在/已被处理时);防止这些情况的最佳方法是,在窗口初始化过程中,不要比Load()事件处理程序更早地启动线程,并通过取消窗口创建的后台线程并等待它们关闭来处理close()事件。

If the application is not multithreaded (you are not setting up BackgroundWorkers, TPL operations, BeginInvoke()ing delegates or Start()ing threads), then it's not necessary. However, calls to InvokeRequired are pretty cheap (the logic behind it is basically a check that the WinAPI functions GetThreadId and GetWindowThreadProcessId return the same value), so if you anticipate the program being restructured to be multithreaded, the following patterns for called methods are simple enough to implement:

如果应用程序不是多线程的(您没有设置BackgroundWorkers、TPL操作、BeginInvoke()委托或启动()线程),那么就没有必要了。但是,调用InvokeRequired的代价非常小(它背后的逻辑基本上是检查WinAPI函数GetThreadId和getwindowprocessid是否返回相同的值),所以如果您预期要重构的程序是多线程的,那么调用方法的以下模式就足够简单了:

//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   
}

#1


8  

If the application is designed to be multithreaded, then cross-threading can occur and thus you need to check for it using InvokeRequired, and either have the method you are calling re-Invoke() itself on the UI thread, or throw an exception that will indicate code is being used improperly. Keep in mind that InvokeRequired will be false in certain circumstances (mainly when the window doesn't have a handle or is being/has been disposed); the best way to prevent these circumstances is to not start threads earlier in the window initialization process than the Load() event handler, and to handle the Closing() event by cancelling background threads created by the window and waiting for them to close.

如果应用程序被设计为多线程的,那么可能会发生交叉线程,因此需要使用InvokeRequired检查它,并在UI线程上使用正在调用的方法reinvoke()本身,或者抛出异常,表明代码使用不当。记住,invokerequerred在某些情况下是假的(主要是当窗口没有句柄或正在/已被处理时);防止这些情况的最佳方法是,在窗口初始化过程中,不要比Load()事件处理程序更早地启动线程,并通过取消窗口创建的后台线程并等待它们关闭来处理close()事件。

If the application is not multithreaded (you are not setting up BackgroundWorkers, TPL operations, BeginInvoke()ing delegates or Start()ing threads), then it's not necessary. However, calls to InvokeRequired are pretty cheap (the logic behind it is basically a check that the WinAPI functions GetThreadId and GetWindowThreadProcessId return the same value), so if you anticipate the program being restructured to be multithreaded, the following patterns for called methods are simple enough to implement:

如果应用程序不是多线程的(您没有设置BackgroundWorkers、TPL操作、BeginInvoke()委托或启动()线程),那么就没有必要了。但是,调用InvokeRequired的代价非常小(它背后的逻辑基本上是检查WinAPI函数GetThreadId和getwindowprocessid是否返回相同的值),所以如果您预期要重构的程序是多线程的,那么调用方法的以下模式就足够简单了:

//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   
}