在创建窗口句柄之前,无法在控件上调用Invoke或BeginInvoke

Geo*_*uer 76 multithreading winforms

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

我从System.Windows.Forms.Form以下地址调用它:

public void Show(string text) {
    label.SafeInvoke(()=>label.Text = text);
    this.Show();
    this.Refresh();
}
Run Code Online (Sandbox Code Playgroud)

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

System.InvalidOperationException 发生了

Message="在创建窗口句柄之前,无法在控件上调用Invoke或BeginInvoke."

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
Run Code Online (Sandbox Code Playgroud)

发生了什么,我该如何解决?我知道这不是形式创建的问题,因为有时它会工作一次并且下次失败,那么问题是什么呢?

PS.我真的很擅长WinForms,有没有人知道一系列很好的文章解释整个模型以及如何使用它?

Gre*_*g D 71

您可能在错误的线程上创建控件.请考虑MSDN中的以下文档:

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

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

当InvokeRequired在后台线程上返回false时,还可以通过检查IsHandleCreated的值来防止这种情况.如果尚未创建控件句柄,则必须等到创建它之后再调用Invoke或BeginInvoke.通常,只有在应用程序的主要表单的构造函数中创建后台线程(如在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();
    }
}
Run Code Online (Sandbox Code Playgroud)

考虑我们SafeInvoke从非gui线程调用尚未创建句柄的控件的情况.

uiElement不是空的,所以我们检查uiElement.InvokeRequired.根据MSDN文档(粗体)InvokeRequired将返回,false因为即使它是在不同的线程上创建的,手柄还没有创建!这将我们发送到else我们检查IsDisposed或立即从后台线程调用提交的操作的条件!

在这一点上,所有的赌注都是关闭的:那个控制因为它的句柄是在一个没有消息泵的线程上创建的,如第二段所述.也许这就是你遇到的情况?

  • 这个答案和 MSDN 文章都是关于 InvokeRequired 返回 false 因为未创建 Handle 。但是当 InvokeRequired 返回 true 后调用 Beginvoke/Invoke 时,OP 会出现异常。当句柄尚未创建时,InvokeRequired 如何返回 true? (2认同)

Mat*_*ieu 35

我发现InvokeRequired不可靠,所以我只是使用

if (!this.IsHandleCreated)
{
    this.CreateHandle();
}
Run Code Online (Sandbox Code Playgroud)

  • 难道这不会导致在不同的线程上创建两个句柄吗?句柄应该被创建,你只需要改善事件的时间/顺序.. (5认同)
  • MSDN:"如果尚未创建控件的句柄,则不应简单地在控件上调用属性,方法或事件.这可能导致在后台线程上创建控件的句柄,从而隔离控件线程没有消息泵,使应用程序不稳定." 练习的重点是避免在错误的线程上创建句柄.如果这个调用是从一个不是gui线程的线程发生的,那么你就死定了. (5认同)

Ben*_*jol 20

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

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

var x = this.Handle; 
Run Code Online (Sandbox Code Playgroud)

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


Arn*_*hea 5

您链接到的帖子中的方法调用Invoke/BeginInvoke,然后检查是否已从未创建控件的线程调用控件的句柄.

因此,当您从创建控件的线程以外的线程调用方法时,您将获得异常.这可能发生在远程事件或排队的工作用户项目中......

编辑

如果在调用invoke之前检查InvokeRequired和HandleCreated,则不应该得到该异常.