85

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

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

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.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

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?

Community
  • 1
  • 1
George Mauer
  • 117,483
  • 131
  • 382
  • 612
  • 1
    Something strange going on with the link...the markup and preview are correct...strange. – George Mauer Apr 30 '09 at 20:27
  • What contexts is Show called in? Is it ever called from a Form's constructor, e.g.? It may be useful to log messages for calls to show against messages triggered by the HandleCreated event to verify that you're only calling show on objects that've already had their handles created. – Greg D Apr 30 '09 at 21:36
  • What is the application for/how is it designed? What does this.Show() do? (I'm assuming it does something more than just this.Visible = true;) Is your reference to webforms a typo? – Greg D May 01 '09 at 00:48
  • this.Show() is the base Form.Show() so whatever that does. The dialog is never brought up from a constructor. It is called by an implementation of an INotifier service which has a simple Notify(string) method – George Mauer May 01 '09 at 17:38
  • 4
    Looking at it again, over a year later, it looks like you're experiencing the error precisely for the reason that the `IsHandleCreated` check exists. You're trying to change a property of (Send a message to) a control that has not yet been created. One thing you can do in this situation is queue up delegates that are submitted prior to the control's creation, then run them in the `HandleCreated` event. – Greg D Aug 23 '10 at 12:25
  • The same exception can also occur if you try to do "Invoke" on a Control that has been disposed/destroyed. – mroman Sep 12 '18 at 15:32

10 Answers10

79

It's possible that you're creating your controls on the wrong thread. Consider the following 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.

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

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

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 SafeInvoke from the non-gui thread for a control whose handle has not been created.

uiElement is not null, so we check uiElement.InvokeRequired. Per the MSDN docs (bolded) InvokeRequired will return false because, even though it was created on a different thread, the handle hasn't been created! This sends us to the else condition where we check IsDisposed or immediately proceed to call the submitted action... from the background thread!

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?

Greg D
  • 43,259
  • 14
  • 84
  • 117
  • Should you include an `EndInvoke` after the `BeginInvoke`? – Odys Dec 17 '12 at 17:18
  • @odyodyodys: Short answer: No. This is a magical, super-specific case where you don't have to. Longer answer: Read the comments on this answer: http://stackoverflow.com/a/714680/6932 – Greg D Dec 17 '12 at 18:51
  • 2
    This answer and the MSDN article is all about InvokeRequired returning false because Handle is not created. But OP is getting exception when Beginvoke/Invoke is called after InvokeRequired returns true. How can InvokeRequired return true, when the handle is not created yet? – thewpfguy Jan 26 '15 at 22:45
  • There's also a race condition, one I've run, into wrt IsDisposed. IsDisposed can be false when tested but become true before the submitted action is fully executed. The two choices seem to be (a) ignore InvalidOperationException and (b) use lock to create critical sections out of the submitted action and the control dispose method. The first feels like a hack and the second is a pain. – blearyeye Oct 07 '16 at 09:10
37

I found the InvokeRequired not reliable, so I simply use

if (!this.IsHandleCreated)
{
    this.CreateHandle();
}
Mathieu
  • 4,449
  • 7
  • 41
  • 60
  • 5
    Couldn't this cause there to be two handles being created on different threads? The handle should get created, you just have to improve your timing/order of events.. – Denise Skidmore Apr 10 '13 at 22:02
  • Nice - I prefer this to just accessing this.Handle as (a) you don't have an unused variable and (b) it's obvious what's going on – Dunc Jun 05 '13 at 11:49
  • 5
    MSDN: "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." The whole point of the exercise is to avoid creating the handle on the wrong thread. If this call happens from a thread that isn't the gui thread, bang- you're dead. – Greg D Sep 19 '14 at 17:29
25

Here is my answer to 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)

Community
  • 1
  • 1
Benjol
  • 63,995
  • 54
  • 186
  • 268
  • Very interesting article btw. Thanks. – Yann Trevin Dec 21 '10 at 19:51
  • Thanks, this worked for me because I had a hidden form which needed to be animated in and out from a background thread. Referencing the Handle was the thing that got it working for me – John Mc Mar 21 '16 at 14:59
  • 1
    This is still a problem in latest versions of .net, although it's less an error than a "feature". Worth noting that putting a "watch" on the object and browsing it's properties also does the same as looking at the handle. You wind up with some quantum debugging nonsense where it works when you look at it. – Tony Cheetham Feb 10 '20 at 23:38
5

The method in the post you link to calls Invoke/BeginInvoke before 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.

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 InvokeRequired and HandleCreated before calling invoke you shouldn't get that exception.

Yousha Aleayoub
  • 4,532
  • 4
  • 53
  • 64
Shea
  • 11,085
  • 2
  • 19
  • 21
  • If I understand correctly, you're saying this will happen whenever the invoking thread is different from the one the control was created on. I cannot guarantee what thread the event will be called from. It could be the one that created it is (likelier) is an entirely different thread. How do I resolve this? – George Mauer Apr 30 '09 at 20:44
  • yep that's correct. I edited the post with a condition that should fix the problem. – Shea Apr 30 '09 at 20:53
  • I'm not convinced this is the case. I've updated my question based on your comment, Arnshea. – Greg D Apr 30 '09 at 20:54
  • 1
    I don't understand. I need that window to show, I'm not clear why IsHandleCreated is false, but not having the window show up is not an option, my question is about why in the world would it be false – George Mauer Apr 30 '09 at 21:03
  • I believe that IsHandleCreated will return false if the handle's been closed/the control's been disposed. Are you certain that you aren't being bitten by an asynchronous invoke on a control that used to exist, but doesn't anymore? – Greg D Apr 30 '09 at 21:06
  • I create the form when I start the app and from there on out just Show() and Hide() it. There's all sorts of threads that these calls could come from but I don't know why it would ever be disposed of until the application ends. – George Mauer Apr 30 '09 at 21:19
3

Add this before you call method invoke:

while (!this.IsHandleCreated) 
   System.Threading.Thread.Sleep(100)
Yousha Aleayoub
  • 4,532
  • 4
  • 53
  • 64
amos godwin
  • 930
  • 9
  • 12
3

If you're going to use a Control from 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 CreateHandle function.

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

lmat - Reinstate Monica
  • 7,289
  • 6
  • 48
  • 62
1

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 InitializeComponent much slower. A better solution is to not background anything before the control has a handle.

sp_m
  • 2,647
  • 8
  • 38
  • 62
joe
  • 19
  • 1
1
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();
Shimon Doodkin
  • 4,310
  • 34
  • 37
0

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 n other 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 object or 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:

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...

rupweb
  • 3,052
  • 1
  • 30
  • 57
0

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