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


81

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

我从System.Windows.Forms.Form以下方式调用它:

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

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

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

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

PS。我真的对WinForms感到很糟糕,是否有人知道一系列很好的文章来解释整个模型以及如何使用它?


1
链接上发生了一些奇怪的事情……标记和预览是正确的……奇怪。
乔治·莫尔

Show调用了哪些上下文?例如,是否曾经从Form的构造函数中调用过它?记录呼叫的消息以针对由HandleCreated事件触发的消息进行显示以验证您是否仅在已经创建了其句柄的对象上呼叫show可能有用。
Greg D

它的用途是什么/如何设计?this.Show()是做什么的?(我假设它所做的不只是this.Visible = true;)您对Webforms的引用是否有错字?
格雷格D

this.Show()是基本Form.Show(),所以无论如何。对话框永远不会从构造函数中调出。它由具有简单Notify(string)方法的INotifier服务的实现调用
George Mauer

4
一年后再次查看时,您似乎正由于IsHandleCreated存在检查的原因而遇到该错误。您正在尝试更改尚未创建的控件的属性(向其发送消息)。在这种情况下,您可以做的一件事是将在控件创建之前提交的委托排队,然后在HandleCreated事件中运行它们。
格雷格D

Answers:


76

您可能在错误的线程上创建控件。考虑以下来自MSDN的文档

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

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

您可以通过在InvokeRequired在后台线程上返回false时也检查IsHandleCreated的值来防止这种情况。如果尚未创建控件句柄,则必须等待它创建后再调用Invoke或BeginInvoke。通常,只有在显示表单或调用Application.Run之前,在应用程序的主窗体的构造函数中创建了后台线程(如Application.Run(new MainForm())一样),才会发生这种情况。

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

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

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

这时,所有的赌注都落在了该控件上,因为它的句柄是在没有消息泵的线程上创建的,如第二段所述。也许就是这种情况?


您是否应在EndInvoke之后加上一个BeginInvoke
奥德斯

@odyodyodys:简短的回答:否。这是一个神奇的,超级特定的情况,您无需这样做。更长的答案:阅读对此答案的评论:stackoverflow.com/a/714680/6932
Greg D

1
这个答案和MSDN文章都是关于InvokeRequired返回false的,因为未创建Handle。但是,当InvokeRequired返回true之后调用Beginvoke / Invoke时,OP正在获取异常。当尚未创建句柄时,InvokeRequired如何返回true?
thewpfguy 2015年

还有一种竞赛条件,我已经遇到了,进入了IsDisposed。测试时,IsDisposed可以为false,但在完全执行提交的操作之前为true。这两个选择似乎是(a)忽略InvalidOperationException和(b)使用锁从提交的操作和控件dispose方法中创建关键部分。第一种感觉很像黑客,第二种感觉很痛苦。
blearyeye

36

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

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

5
这是否会导致在不同线程上创建两个句柄?应该创建句柄,您只需要改善事件的时间/顺序即可
。–

尼斯-我更喜欢此方法而不是访问this.Handle,因为(a)您没有未使用的变量,并且(b)显然发生了什么
Dunc

5
MSDN:“在尚未创建控件的句柄的情况下,您不应简单地在控件上调用属性,方法或事件。这可能会导致在后台线程上创建控件的句柄,从而将控件隔离在没有消息泵的线程导致应用程序不稳定。” 整个练习的重点是避免在错误的线程上创建句柄。如果此调用是从不是gui线程的线程发生的,那么bang-您已经死了。
Greg D

25

这是我回答了类似的问题

认为(尚未完全确定)这是因为如果尚未加载/显示控件,则InvokeRequired将始终返回false。我已经完成了一项暂时解决的工作,即简单地引用其创建者中关联控件的句柄,如下所示:

var x = this.Handle; 

(请参阅 http://ikriv.com/en/prog/info/dotnet/MysteriousHang.html


顺便说一句非常有趣的文章。谢谢。
Yann Trevin 2010年

谢谢,这对我有用,因为我有一个隐藏的表单,需要在后台线程中对其进行动画处理。引用句柄是让它为我工作的原因
John Mc

在.net的最新版本中,这仍然是一个问题,尽管它不是“功能”而是错误。值得一提的是,在对象上放置“监视”并浏览其属性也与查看手柄相同。当您查看它时,您会发现一些量子调试的废话在哪里起作用。
Tony Cheetham

5

链接到帖子中的方法将调用Invoke/,BeginInvoke然后再从未创建控件的线程中调用该控件的句柄之前,先检查该控件的句柄是否已创建。

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

编辑

如果检查InvokeRequired并且HandleCreated在调用invoke之前不应该获得该异常。


如果我理解正确,那么您会说,只要调用线程与创建控件的线程不同,就会发生这种情况。我不能保证将从哪个线程调用该事件。它可能是创建它的那个线程(可能)是一个完全不同的线程。我该如何解决?
乔治·莫尔

是的,这是正确的。我以应解决问题的条件编辑了帖子。
乳木果

我不认为是这种情况。我已经根据您的评论更新了我的问题,Arnshea。
Greg D

我不明白 我需要显示该窗口,我不清楚为什么IsHandleCreated是false,但是不显示窗口不是一种选择,我的问题是为什么在世界上它会是false
George Mauer

我相信,如果句柄已关闭/控件已处置,则IsHandleCreated将返回false。您确定您不会被以前存在但不再存在的控件的异步调用所困扰吗?
Greg D

3

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

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


3

在调用方法调用之前添加以下内容:

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

1

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

注意:请谨慎使用此解决方案。如果控件具有句柄,则执行设置其大小和位置之类的操作要慢得多。这使InitializeComponent变慢得多。更好的解决方案是在控件具有句柄之前不后台处理任何内容。


0

我有这种简单形式的问题:

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线程实例的句柄,因此其他线程获得了其他实例句柄,即Object reference not set to an instance of an objector 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; });
    }
}

到目前为止,我猜它工作正常。


0
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样式的技术。
乔治·莫尔

确保尝试明确指出要调用的内容。-随便
Shimon Doodkin,

0

那这个呢 :


    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;
    }
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.