在创build窗口句柄之前,不能在控件上调用Invoke或BeginInvoke

我有一个类似于Greg D在此讨论的SafeInvoke Control扩展方法(减去IsHandleCreated检查)。

我从System.Windows.Forms.Form调用它,如下所示:

 public void Show(string text) { label.SafeInvoke(()=>label.Text = text); this.Show(); this.Refresh(); } 

有时(这个调用可能来自各种线程),导致以下错误:

发生System.InvalidOperationException

Message =“Invoke或BeginInvoke不能在控件上调用,直到窗口句柄已被创build”。

Source =“System.Windows.Forms”

 StackTrace: at System.Windows.Forms.Control.MarshaledInvoke(Control caller, Delegate method, Object[] args, Boolean synchronous) at System.Windows.Forms.Control.Invoke(Delegate method, Object[] args) at System.Windows.Forms.Control.Invoke(Delegate method) at DriverInterface2.UI.WinForms.Dialogs.FormExtensions.SafeInvoke[T](T control, Action`1 action) in C:\code\DriverInterface2\DriverInterface2.UI.WinForms\Dialogs\FormExtensions.cs:line 16 

怎么回事,我该如何解决? 我知道它不是一个forms创造的问题,因为有时它会工作一次,下一次失败,那么问题是什么?

PS。 我真的非常害怕WinForms,有没有人知道一系列文章解释整个模型,以及如何使用它?

您可能是在错误的线程上创build了控件。 考虑从MSDN下面的文档 :

这意味着,如果Invoke不是必需的(调用发生在同一线程上),或者如果控件是在不同的线程上创build的,但控件的句柄尚未创build ,则InvokeRequired可以返回false

在控件的句柄尚未创build的情况下,不应该简单地调用控件上的属性,方法或事件。 这可能会导致在后台线程上创build控件的句柄,在没有消息泵的情况下将线程上的控件隔离,并使应用程序不稳定。

当InvokeRequired在后台线程返回false时,可以通过检查IsHandleCreated的值来防止这种情况。 如果控制句柄尚未创build,则必须等到创build控制句柄后再调用Invoke或BeginInvoke。 通常,只有在应用程序的主窗体的构造函数中创build后台线程(如在Application.Run(new MainForm())中,在窗体已经显示或Application.Run被调用之前,才会发生这种情况。

让我们看看这对你意味着什么。 (如果我们看到您的SafeInvoke的实现,这将更容易推理)

假设你的实现和被引用的实现是相同的,除了检查IsHandleCreated之外 ,让我们按照下面的逻辑:

 public static void SafeInvoke(this Control uiElement, Action updater, bool forceSynchronous) { if (uiElement == null) { throw new ArgumentNullException("uiElement"); } if (uiElement.InvokeRequired) { if (forceSynchronous) { uiElement.Invoke((Action)delegate { SafeInvoke(uiElement, updater, forceSynchronous); }); } else { uiElement.BeginInvoke((Action)delegate { SafeInvoke(uiElement, updater, forceSynchronous); }); } } else { if (uiElement.IsDisposed) { throw new ObjectDisposedException("Control is already disposed."); } updater(); } } 

考虑一下我们从non-gui线程调用其句柄尚未创build的控件的SafeInvoke的情况。

uiElement不为null,所以我们检查uiElement.InvokeRequired 。 根据MSDN文档(粗体) InvokeRequired将返回false因为即使它创build在不同的线程上,句柄尚未创build! 这将我们发送到else条件,我们检查IsDisposed或立即继续调用从后台线程提交的行动!

在这一点上,所有的赌注都是closures的:这个控制是因为它的句柄已经在一个没有消息泵的线程上创build,如第二段所述。 也许这是你遇到的情况?

我发现InvokeRequired不可靠,所以我简单地使用

 if (!this.IsHandleCreated) { this.CreateHandle(); } 

这是我对类似问题的回答 :

认为 (不完全确定),这是因为如果控件尚未加载/显示,InvokeRequired将始终返回false。 我已经做了一个似乎现在可以工作的解决方法,就是简单地引用其创build者中的相关控件的句柄,如下所示:

 var x = this.Handle; 

(见http://ikriv.com/en/prog/info/dotnet/MysteriousHang.html

在链接调用Invoke / BeginInvoke之前,检查是否在从未创build控件的线程中调用控件的句柄之前,该方法中的方法。

所以当你从一个非创build控件的线程调用你的方法时,你会得到exception。 这可能发生在远程事件或排队的工作用户项目…

编辑

如果你在调用invoke之前检查了InvokeRequired和HandleCreated,你就不应该得到这个exception。

如果要在使用Control显示或执行其他操作之前使用另一个线程的Control ,请考虑在构造函数中强制创build其句柄。 这是使用CreateHandle函数完成的。

在“控制器”逻辑不在WinForm中的multithreading项目中,此函数有助于Control构造函数以避免此错误。

在其创build者中引用相关控件的句柄,如下所示:

注意 :要小心这个解决scheme。如果一个控件有一个句柄,比如设置它的大小和位置要慢很多。 这使得InitializeComponent慢得多。 更好的解决scheme是在控件处理之前不要背景。

我遇到了同样的错误。 我正在调用Form()构造函数的调用。

我通过调用Form_Load事件的调用来解决这个问题。

我做了这个改变后,我没有看到任何例外。