使用 Invoke 方法关闭外部线程中的窗体

本文关键字:线程 窗体 外部 Invoke 方法 使用 | 更新日期: 2023-09-27 18:30:29

我必须从线程关闭一个表单,并且我正在使用表单的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);
    }

如果有人能给我一个解决方案或其他从另一个线程关闭表单的方法,我将不胜感激。

使用 Invoke 方法关闭外部线程中的窗体

使用此方法:

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

到目前为止,这种情况的最佳解决方案是使用 SynchronizationContext 机制。我在应该使用调用还是同步上下文从另一个线程更新表单控件?中得到了提示。

示例代码如下所示:

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

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

不过既然你问了...

该错误为您提供了答案 - 在构建之前不要关闭。 设置表单计时器 - 在所有其他表单创建消息之前,不会处理谁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);
}

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

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