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

Posted

技术标签:

【中文标题】在创建窗口句柄之前,不能对控件调用 Invoke 或 BeginInvoke【英文标题】:Invoke or BeginInvoke cannot be called on a control until the window handle has been created 【发布时间】:2010-10-22 23:20:30 【问题描述】:

我有一个类似于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 发生

Message= "。"

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,有没有人知道解释整个模型以及如何使用它的一系列好文章?

【问题讨论】:

链接发生了一些奇怪的事情......标记和预览是正确的......奇怪。 Show 在什么上下文中被调用?它是否曾经从表单的构造函数中调用过,例如?根据 HandleCreated 事件触发的消息记录调用 show 的消息可能很有用,以验证您仅在已创建句柄的对象上调用 show。 应用程序的用途/设计方式是什么? this.Show() 有什么作用? (我假设它不仅仅是 this.Visible = true;)你对 webforms 的引用是错字吗? this.Show() 是基本的 Form.Show() 所以不管它做什么。对话框永远不会从构造函数中调出。它由具有简单 Notify(string) 方法的 INotifier 服务的实现调用 再看一遍,一年多之后,您似乎正是因为存在IsHandleCreated 检查而遇到了错误。您正在尝试更改尚未创建的控件的 (Send a message to) 属性。在这种情况下,您可以做的一件事是将在创建控件之前提交的委托排队,然后在 HandleCreated 事件中运行它们。 【参考方案1】:

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

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

在控件的句柄的情况下 尚未创建,您应该 不仅仅是调用属性、方法, 或控件上的事件。这有可能 导致控件的句柄是 在后台线程上创建, 隔离线程上的控件 没有消息泵并制作 应用不稳定。

您可以通过以下方式防范这种情况 还检查的值 IsHandleCreated when InvokeRequired 在后台线程上返回 false。 如果控制手柄尚未 创建,您必须等到它有 在调用 Invoke 或之前创建 开始调用。通常,会发生这种情况 仅在创建后台线程时 在主要形式的构造函数中 对于应用程序(如 Application.Run(新 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();
    

考虑我们从非 gui 线程调用 SafeInvoke 以获取未创建句柄的控件的情况。

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

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

【讨论】:

是否应该在BeginInvoke 之后添加EndInvoke @odyodyodys:简短回答:不。这是一个神奇的、超级具体的案例,您不必这样做。更长的答案:阅读有关此答案的 cmets:***.com/a/714680/6932 这个答案和 MSDN 文章都是关于 InvokeRequired 返回 false 因为未创建句柄。但是当 InvokeRequired 返回 true 后调用 Beginvoke/Invoke 时,OP 会出现异常。尚未创建句柄时,InvokeRequired 如何返回 true? 还有一个竞争条件,我已经运行过,进入 wrt IsDisposed。 IsDisposed 在测试时可能为假,但在提交的操作完全执行之前变为真。这两种选择似乎是 (a) 忽略 InvalidOperationException 和 (b) 使用锁从提交的操作和控制处置方法中创建关键部分。第一个感觉像 hack,第二个感觉很痛苦。【参考方案2】:

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

if (!this.IsHandleCreated)

    this.CreateHandle();

【讨论】:

这不会导致在不同的线程上创建两个句柄吗?应该创建句柄,您只需要改进事件的时间/顺序.. 很好 - 我更喜欢这个而不是访问这个。处理为(a)你没有未使用的变量,并且(b)很明显发生了什么 MSDN:“在尚未创建控件句柄的情况下,不应简单地调用控件的属性、方法或事件。这可能会导致控件的句柄在后台线程,在没有消息泵的情况下隔离线程上的控制并使应用程序不稳定。”练习的重点是避免在错误的线程上创建句柄。如果这个调用发生在一个不是 gui 线程的线程中,那么你就死定了。【参考方案3】:

这是我的answer 和类似的question:

认为(还不完全确定) 这是因为 InvokeRequired 将 如果控件有,则始终返回 false 尚未加载/显示。我已经做好了 一种似乎适用的解决方法 那一刻,很简单 引用关联的句柄 控制其创建者,如下所示:

var x = this.Handle; 

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

【讨论】:

非常有趣的文章顺便说一句。谢谢。 谢谢,这对我有用,因为我有一个隐藏的表单,需要在后台线程中进出动画。引用句柄是它为我工作的原因 这在最新版本的 .net 中仍然是一个问题,尽管它比“功能”还少。值得注意的是,在对象上放置一个“监视”并浏览它的属性也与查看句柄相同。当你看到它时,你会发现一些量子调试的废话。【参考方案4】:

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

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

编辑

如果您在调用 invoke 之前检查 InvokeRequiredHandleCreated,则不应出现该异常。

【讨论】:

如果我理解正确,您是说只要调用线程与创建控件的线程不同,就会发生这种情况。我无法保证将从哪个线程调用事件。它可能是创建它的那个(很可能)是一个完全不同的线程。我该如何解决这个问题? 是的,没错。我用一个应该可以解决问题的条件编辑了帖子。 我不相信是这种情况。我根据你的评论更新了我的问题,Arnshea。 我不明白。我需要显示那个窗口,我不清楚为什么 IsHandleCreated 是假的,但不显示窗口不是一种选择,我的问题是为什么它会是假的 我相信,如果句柄已关闭/控件已被处置,IsHandleCreated 将返回 false。您确定您没有被曾经存在但现在不存在的控件上的异步调用所困扰吗?【参考方案5】:

在调用方法调用之前添加:

while (!this.IsHandleCreated) 
   System.Threading.Thread.Sleep(100)

【讨论】:

【参考方案6】:

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

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

【讨论】:

【参考方案7】:

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

注意:请注意此解决方案。如果控件有句柄,则设置它的大小和位置等操作会慢得多。这使得 InitializeComponent 慢得多。更好的解决方案是在控件有句柄之前不做任何背景。

【讨论】:

【参考方案8】:
var that = this; // this is a form
(new Thread(()=> 

    var action= new Action(() => 
       something
    ));

    if(!that.IsDisposed)
    
        if(that.IsHandleCreated)
        
            //if (that.InvokeRequired)
                that.BeginInvoke(action);
            //else
            // action.Invoke();
        
        else
            that.HandleCreated+=(sender,event) => 
                action.Invoke();
            ;
    


)).Start();

【讨论】:

这是 c# - this 不会因调用而异,应该不需要 javascript 样式的技术。 肯定试图明确调用什么。 - 随便【参考方案9】:

这种简单的形式我遇到了这个问题:

public partial class MyForm : Form

    public MyForm()
    
        Load += new EventHandler(Form1_Load);
    

    private void Form1_Load(Object sender, EventArgs e)
    
        InitializeComponent();
    

    internal void UpdateLabel(string s)
    
        Invoke(new Action(() =>  label1.Text = s; ));
    

然后对于 n 其他异步线程,我使用 new MyForm().UpdateLabel(text) 尝试调用 UI 线程,但构造函数没有为 UI 线程实例提供句柄,因此其他线程获取其他实例句柄,它们要么是 @987654324 @ 或 Invoke or BeginInvoke cannot be called on a control until the window handle has been created。为了解决这个问题,我使用了一个静态对象来保存 UI 句柄:

public partial class MyForm : Form

    private static MyForm _mf;        

    public MyForm()
    
        Load += new EventHandler(Form1_Load);
    

    private void Form1_Load(Object sender, EventArgs e)
    
        InitializeComponent();
        _mf = this;
    

    internal void UpdateLabel(string s)
    
        _mf.Invoke((MethodInvoker) delegate  _mf.label1.Text = s; );
    

我想它工作正常,到目前为止......

【讨论】:

【参考方案10】:

这个呢:


    public static bool SafeInvoke( this Control control, MethodInvoker method )
    
        if( control != null && ! control.IsDisposed && control.IsHandleCreated && control.FindForm().IsHandleCreated )
        
            if( control.InvokeRequired )
            
                control.Invoke( method );
            
            else
            
                method();
            
            return true;
        
        else return false;
    

【讨论】:

以上是关于在创建窗口句柄之前,不能对控件调用 Invoke 或 BeginInvoke的主要内容,如果未能解决你的问题,请参考以下文章

c#,求助!在创建窗口句柄之前,不能在控件上调用 Invoke 或 BeginInvoke。

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

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

SQL 2008 management studio安装时出现错误:在创建窗口句柄之前,不能在控件上调用InVoke或BeginInvoke

Invoke()/BeginInvoke()区别

多线程委托Invoke解决winform界面卡死的问题,并带开关