4

I have to close a Form from a thread and I am using the Invoke method of the Form for calling the Close() method.

The problem is that when closing, the form is disposed and I get an InvalidOperationExecption wit the message "Invoke or BeginInvoke cannot be called on a control until the window handle has been created.".

I have got this exception only when debugging with a "Step Into" in the Close method but I don't want to risk with a possible error on normal running.

This is an example code to reproduce it:

 private void Form1_Load(object sender, EventArgs e)
 {
     Thread thread = new Thread(CloseForm);
     thread.Start();
 }

 private void CloseForm()
 {
     this.Invoke(new EventHandler(
         delegate
         {
             Close(); // Entering with a "Step Into" here it crashes.
         } 
     ));
 }

The form is disposed in the automatic generated code for the form (which I would like not to modify):

    protected override void Dispose(bool disposing)
    {
        if (disposing && (components != null))
        {
            components.Dispose();
        }
        base.Dispose(disposing);
    }

I would appreciate it if someone could give me a solution for this or another way to close a form from another thread.

tshepang
  • 12,111
  • 21
  • 91
  • 136
Alfort
  • 91
  • 1
  • 1
  • 9
  • No repro with the posted code, I didn't expect one. The generic reason for this exception is that your thread keeps calling Begin/Invoke *after* the form is already closed. http://stackoverflow.com/questions/1731384/how-to-stop-backgroundworker-on-forms-closing-event/1732361#1732361 – Hans Passant Apr 11 '12 at 09:12
  • Can't reproduce exception with code you provided – Sergey Berezovskiy Apr 11 '12 at 18:05
  • It is very easy to reproduce, just using the "Step Into" option and assuring that it enters (maybe a breakpoint can help) in the Dispose method before leaving the Invoke method. – Alfort Apr 11 '12 at 18:44
  • @HansPassant although in this post they don't give the answer, in my case, and just because my thread finishes when the form has to close, I have seen that I can use a BackgroundWorker and in its RunWorkerCompleted event call Close() without the Invoke way. Thanks. The question is still opened... – Alfort Apr 11 '12 at 18:59

5 Answers5

5

Use this method :

// Inspired from: http://stackoverflow.com/a/12179408/1529139
public static void InvokeIfRequired(Control control, MethodInvoker action)
{
    if (control.IsDisposed)
    {
        return;
    }

    if (control.InvokeRequired)
    {
        try
        {
            control.Invoke(action);
        }
        catch (ObjectDisposedException) { }
        catch (InvalidOperationException e)
        {
            // Intercept only invokation errors (a bit tricky)
            if (!e.Message.Contains("Invoke"))
            {
                throw e;
            }
        }
    }
    else
    {
        action();
    }
}

Usage example:

Functions.InvokeIfRequired(anyControl, (MethodInvoker)delegate()
{
    // UI stuffs
});
56ka
  • 1,463
  • 1
  • 21
  • 37
1

So far the best solution for this case has been to use the SynchronizationContext mechanism. I had the tip in Should I use Invoke or SynchronizationContext to update form controls from another thread?.

The example code would be like this:

private void Form1_Load(object sender, EventArgs e)
{
    Thread thread = new Thread(MethodThread);
    thread.Start(SynchronizationContext.Current);
}

private void MethodThread(Object syncronizationContext)
{
    ((SynchronizationContext)syncronizationContext).Send(CloseForm,null);
}

private void CloseForm(Object state)
{
    Close();
}
Community
  • 1
  • 1
Alfort
  • 91
  • 1
  • 1
  • 9
0

The most obvious comment is - there's no apparent reason why you would need to close a form before it has even completed loading. There are other, better ways to handle whatever the reason is.

However since you asked...

The error gives you the answer - do not close until it has been constructed. Setup a Forms Timer - who's WM_TIMER message won't be processed until all other form creation messages are.

private System.Windows.Forms.Timer _timer;
protected override void OnLoad(EventArgs args)
{
    _timer = new Timer { Interval = 1 };
    _timer.Tick += (s, e) => new Thread(CloseForm).Start();
    _timer.Start();

    base.OnLoad(args);
}
John Arlen
  • 6,539
  • 2
  • 33
  • 42
  • Thanks for your answer. Maybe I didn't put an easy example. My code is of course different.I believe that the load event is created after the handle of the form is created but anyway the error is still there and with your example code the exception is also raised. – Alfort Apr 11 '12 at 07:37
0

While I feel that there must be a clean way to do this without platform interop, I can't think what it is. In the meantime, here's some code showing an approach that certainly works, assuming you don't mind the p/invoke...

public partial class Form1 : Form
{
    private const uint WM_CLOSE = 0x0010;
    private IntPtr _myHandle;

    public Form1()
    {
        InitializeComponent();
    }

    private void Form1_Load(object sender, EventArgs e)
    {
        var t = new Thread(ThreadProc);
        t.Start();
    }

    protected override void OnHandleCreated(EventArgs e)
    {
        _myHandle = this.Handle;
        base.OnHandleCreated(e);
    }

    [return: MarshalAs(UnmanagedType.Bool)]
    [DllImport("user32.dll", SetLastError = true)]
    static extern bool PostMessage(IntPtr hWnd, uint Msg, IntPtr wParam, IntPtr lParam);

    private void ThreadProc(object o)
    {
        Thread.Sleep(5000);
        PostMessage(_myHandle, WM_CLOSE, IntPtr.Zero, IntPtr.Zero);
    }
}
Martin
  • 5,392
  • 30
  • 39
0

I ran into a similar situation this morning where I was calling Close in an Invoke call and getting the InvalidOperationException when the Close method tried to return. The Invoke method is not able to return a value to the caller since it has been disposed. To sole this problem, I used BeginInvoke instead which allowed my thread to return before the form was closed.

Matt
  • 49
  • 1
  • 3