Close a form from an external thread using the Invoke method

徘徊边缘 提交于 2019-12-07 09:24:49

问题


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.


回答1:


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



回答2:


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



回答3:


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



回答4:


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



回答5:


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.



来源:https://stackoverflow.com/questions/10084691/close-a-form-from-an-external-thread-using-the-invoke-method

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!