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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/808867/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-10 01:02:02  来源:igfitidea点击:

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

winformsmultithreading

提问by George Mauer

I have a SafeInvoke Control extension method similar to the one Greg D discusses here(minus the IsHandleCreated check).

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

I am calling it from a System.Windows.Forms.Formas follows:

我从 a 调用它System.Windows.Forms.Form如下:

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

Sometimes (this call can come from a variety of threads) this results in the following error:

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

System.InvalidOperationExceptionoccurred

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

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

What is going on and how do I fix it? I know as much as it is not a problem of form creation, since sometimes it will work once and fail the next time so what could the problem be?

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

PS. I really really am awful at WinForms, does anyone know a good series of articles that explains the whole model and how to work with it?

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

采纳答案by Greg D

It's possible that you're creating your controls on the wrong thread. Consider the following documentation from MSDN:

您可能在错误的线程上创建控件。考虑来自 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.

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

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

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

Let's see what this means for you. (This would be easier to reason about if we saw your implementation of SafeInvoke also)

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

Assuming your implementation is identical to the referenced one with the exception of the check against IsHandleCreated, let's follow the logic:

假设您的实现与引用的相同,但检查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();
    }
}

Consider the case where we're calling SafeInvokefrom the non-gui thread for a control whose handle has not been created.

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

uiElementis not null, so we check uiElement.InvokeRequired. Per the MSDN docs (bolded) InvokeRequiredwill return falsebecause, even though it was created on a different thread, the handle hasn't been created! This sends us to the elsecondition where we check IsDisposedor immediately proceed to call the submitted action... from the background thread!

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

At this point, all bets are off re: that control because its handle has been created on a thread that doesn't have a message pump for it, as mentioned in the second paragraph. Perhaps this is the case you're encountering?

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

回答by Mathieu

I found the InvokeRequirednot reliable, so I simply use

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

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

回答by Benjol

Here is my answerto a similar question:

这是我回答了类似的问题

I think(not yet entirely sure) that this is because InvokeRequired will always return false if the control has not yet been loaded/shown. I have done a workaround which seems to work for the moment, which is to simple reference the handle of the associated control in its creator, like so:

var x = this.Handle; 

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

认为(尚不完全确定)这是因为如果尚未加载/显示控件,则 InvokeRequired 将始终返回 false。我已经完成了一个目前似乎有效的解决方法,即在其创建者中简单地引用关联控件的句柄,如下所示:

var x = this.Handle; 

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

回答by Shea

The method in the post you link to calls Invoke/BeginInvokebefore checking if the control's handle has been created in the case where it's being called from a thread that didn't create the control.

您链接到的帖子中的方法调用Invoke/BeginInvoke之前检查控件的句柄是否已创建,如果它是从未创建控件的线程调用的。

So you'll get the exception when your method is called from a thread other than the one that created the control. This can happen from remoting events or queued work user items...

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

EDIT

编辑

If you check InvokeRequiredand HandleCreatedbefore calling invoke you shouldn't get that exception.

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

回答by Limited Atonement

If you're going to use a Controlfrom another thread before showing or doing other things with the Control, consider forcing the creation of its handle within the constructor. This is done using the CreateHandlefunction.

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

In a multi-threaded project, where the "controller" logic isn't in a WinForm, this function is instrumental in Controlconstructors for avoiding this error.

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

回答by amos godwin

Add this before you call method invoke:

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

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

回答by joe

Reference the handle of the associated control in its creator, like so:

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

Note: Be wary of this solution.If a control has a handle it is much slower to do things like set the size and location of it. This makes InitializeComponentmuch slower. A better solution is to not background anything before the control has a handle.

注意:小心这个解决方案。如果一个控件有一个句柄,那么设置它的大小和位置等操作会慢得多。这使得InitializeComponent慢得多。更好的解决方案是在控件具有句柄之前不进行任何背景处理。

回答by Gourou Dsecours

What about this :

那这个呢 :


    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 rupweb

I had this problem with this kind of simple form:

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

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; }));
    }
}

Then for nother async threads I was using new MyForm().UpdateLabel(text)to try and call the UI thread, but the constructor gives no handle to the UI thread instance, so other threads get other instance handles, which are either 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. To solve this I used a static object to hold the UI handle:

然后对于nnew MyForm().UpdateLabel(text)用来尝试调用 UI 线程的其他异步线程,但构造函数没有提供 UI 线程实例的句柄,因此其他线程获得其他实例句柄,它们是Object reference not set to an instance of an objectInvoke 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; });
    }
}

I guess it's working fine, so far...

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

回答by Shimon Doodkin

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();