【问题标题】:Close a form from an external thread using the Invoke method使用 Invoke 方法从外部线程关闭表单
【发布时间】:2012-04-10 07:21:45
【问题描述】:

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

如果有人能给我一个解决方案来解决这个问题或以其他方式从另一个线程关闭表单,我将不胜感激。

【问题讨论】:

  • 发布的代码没有重现,我没想到会有。此异常的一般原因是您的线程在表单已经关闭之后继续调用 Begin/Invoke stackoverflow.com/questions/1731384/…
  • 无法用您提供的代码重现异常
  • 它很容易重现,只需使用“Step Into”选项并确保它在离开 Invoke 方法之前进入 Dispose 方法(也许断点会有所帮助)。
  • @HansPassant 虽然在这篇文章中他们没有给出答案,但就我而言,仅仅因为我的线程在表单必须关闭时完成,我已经看到我可以使用 BackgroundWorker 并在它的 RunWorkerCompleted 事件调用 Close() 没有 Invoke 方式。谢谢。问题仍然悬而未决...

标签: c# multithreading winforms invoke


【解决方案1】:

使用这个方法:

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

【讨论】:

    【解决方案2】:

    到目前为止,这种情况的最佳解决方案是使用 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();
    }
    

    【讨论】:

      【解决方案3】:

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

      不过既然你问了……

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

      【讨论】:

      • 感谢您的回答。也许我没有举一个简单的例子。我的代码当然是不同的。我相信加载事件是在创建表单句柄之后创建的,但无论如何错误仍然存​​在,并且您的示例代码也会引发异常。
      【解决方案4】:

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

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

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2011-07-31
          • 2014-07-11
          • 1970-01-01
          • 2010-11-03
          • 2013-08-21
          • 1970-01-01
          相关资源
          最近更新 更多