使用Invoke方法从外部线程关闭表单

时间:2012-04-10 07:21:45

标签: c# multithreading winforms invoke

我必须从一个线程关闭一个Form,我使用Form的Invoke方法来调用Close()方法。

问题是,当关闭时,表单被释放,我得到一个InvalidOperationExecption,消息“在创建窗口句柄之前,无法在控件上调用Invoke或BeginInvoke。”。

只有在Close方法中使用“Step Into”进行调试时才会出现此异常,但我不想冒险在正常运行时出现错误。

这是重现它的示例代码:

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

表单在表单的自动生成代码中处理(我不想修改):

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

如果有人可以通过这种或其他方式从另一个帖子中关闭表单,我会很感激。

5 个答案:

答案 0 :(得分:5)

使用此方法:

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

用法示例:

Functions.InvokeIfRequired(anyControl, (MethodInvoker)delegate()
{
    // UI stuffs
});

答案 1 :(得分:1)

到目前为止,针对此案例的最佳解决方案是使用SynchronizationContext机制。我在Should I use Invoke or SynchronizationContext to update form controls from another thread?获得了提示。

示例代码如下:

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

答案 2 :(得分:0)

最明显的评论是 - 没有明显的理由说明为什么在完成加载之前需要关闭表单。还有其他更好的方法来处理任何原因。

然而,你问过......

错误为您提供答案 - 在构建之前不要关闭。设置表单计时器 - 在所有其他表单创建消息出现之前,不会处理谁的WM_TIMER消息。

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

答案 3 :(得分:0)

虽然我觉得必须有一个干净的方法来做这个没有平台互操作,我想不出它是什么。与此同时,这里有一些代码显示了一种肯定有效的方法,假设你不介意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);
    }
}

答案 4 :(得分:0)

今天早上我遇到了类似的情况,我在Invoke调用中调用Close,并在Close方法尝试返回时获取InvalidOperationException。 Invoke方法无法将值返回给调用者,因为它已被释放。为了解决这个问题,我使用了BeginInvoke,它允许我的线程在表单关闭之前返回。