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

标签 winforms multithreading

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

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

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

有时(此调用可以来自多个线程)这会导致以下错误:

System.InvalidOperationException occurred

Message= "Invoke or BeginInvoke cannot be called on a control until the window handle has been created."

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

这是怎么回事以及如何解决?我知道这不是表单创建的问题,因为有时它会工作一次,下一次就会失败,那么问题可能是什么?

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

最佳答案

您可能在错误的线程上创建控件。考虑以下documentation from MSDN :

This means that InvokeRequired can return false if Invoke is not required (the call occurs on the same thread), or if the control was created on a different thread but the control's handle has not yet been created.

In the case where the control's handle has not yet been created, you should not simply call properties, methods, or events on the control. This might cause the control's handle to be created on the background thread, isolating the control on a thread without a message pump and making the application unstable.

You can protect against this case by also checking the value of IsHandleCreated when InvokeRequired returns false on a background thread. If the control handle has not yet been created, you must wait until it has been created before calling Invoke or BeginInvoke. Typically, this happens only if a background thread is created in the constructor of the primary form for the application (as in Application.Run(new MainForm()), before the form has been shown or Application.Run has been called.

让我们看看这对您意味着什么。 (如果我们也看到您对 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();
    }
}

考虑这样的情况:我们从非 GUI 线程为尚未创建句柄的控件调用 SafeInvoke

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

此时,所有的赌注都已取消:该控件,因为它的句柄是在没有消息泵的线程上创建的,如第二段中所述。也许这就是您遇到的情况?

关于winforms - 在创建窗口句柄之前,无法在控件上调用 Invoke 或 BeginInvoke,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/808867/

相关文章:

c# - winform datagridview 在 CellValueChanged 期间更新值

c# - 替换 Windows 窗体中默认的未处理错误对话框

c# - 避免只读文本框上的选项卡索引

java - 如何等待 AdMob 奖励视频正确加载

c# - 等待所有线程完成的有用模式是什么?

java - 多个客户端-Java中的服务器

windows - Windows 窗体中的主窗体

java - 有没有办法在 Java 应用程序中完全禁用 RMI?

单独的线程锁父级中的 Java JDBC 查询

c# - VB.NET 窗体中的 AppSettings 如何工作?