4

我们在尚未处理的表单上收到ObjectDisposedExceptionfrom 调用。Invoke这是一些演示问题的示例代码:

public partial class Form2 : Form
{
    void Form2_Load(object sender, EventArgs e)
    {
        // Start a task that does an Invoke on this control
        Task.Factory.StartNew(TaskWork); 

        // Sleep here long enough to allow the task that does the Invoke 
        // to execute to the point where it has:
        // a. Posted the message and 
        // b. is waiting 
        Thread.Sleep(500);

        // Cause ShowDialog to return by setting the DialogResult
        DialogResult = DialogResult.OK;
    }

    void TaskWork()
    {
        // This call doesn't return, but instead throws an ObjectDisposedException
        this.Invoke((MethodInvoker)(() => MessageBox.Show("Invoke succeeded")));
    }
}

这是我从不关闭的 Form1(主窗体)的调用代码:

public partial class Form1 : Form
{
    Form2 m_form2 = new Form2();

    void Form1_Load(object sender, EventArgs e)
    {
        // Call ShowDialog, but don't dispose it.
        m_form2.ShowDialog();

        // Cause the finalizers to run.  This causes an AggregateException to be thrown
        // due to the unhandled ObjectDisposedException from the Task.
        GC.Collect(); 
    }
}

我们深入研究了 Microsoft 源代码,发现异常是在调用 DestroyHandle 期间创建的(如下)。在完成时,ShowDialog 正在调用 DestroyHandle。

来自 source.NET\4\DEVDIV_TFS\Dev10\Releases\RTMRel\ndp\fx\src\WinForms\Managed\System\WinForms\Control.cs\1305376\Control.cs:

protected virtual void DestroyHandle() {
    // ...
        // If we're not recreating the handle, then any items in the thread callback list will
        // be orphaned.  An orphaned item is bad, because it will cause the thread to never 
        // wake up.  So, we put exceptions into all these items and wake up all threads. 
        // If we are recreating the handle, then we're fine because recreation will re-post
        // the thread callback message to the new handle for us. 
        //
        if (!RecreatingHandle) {
            if (threadCallbackList != null) {
                lock (threadCallbackList) { 
                    Exception ex = new System.ObjectDisposedException(GetType().Name);

                    while (threadCallbackList.Count > 0) { 
                        ThreadMethodEntry entry = (ThreadMethodEntry)threadCallbackList.Dequeue();
                        entry.exception = ex; 
                        entry.Complete();
                    }
                }
            } 
        }
    // ...
}    

问题:

  1. 为什么 ShowDialog 会破坏句柄(当我可能重新使用此表单时)?

  2. 为什么我得到一个 ObjectDisposedException - 它非常具有误导性(因为它没有被处理)。就好像代码期望只有在对象被释放时才会销毁句柄(这是我所期望的)。

  3. 这应该有效吗?也就是说,是否应该允许我在 ShowDialog 之后调用控件?

笔记:

  1. 再做一次.ShowDialog()会导致创建一个新句柄。

  2. 如果在.ShowDialog()我尝试执行操作之后Invoke,我会收到一个 InvalidOperationException,指出“在创建窗口句柄之前,不能在控件上调用 Invoke 或 BeginInvoke。”

  3. 如果在完成后.ShowDialog()我访问该Handle属性,然后执行一个Invoke,它将成功。

4

1 回答 1

4

为什么 ShowDialog 会破坏句柄(当我可能重新使用此表单时)?

销毁本机窗口并使其消失。此后,Handle 属性将为 IntPtr.Zero。

为什么我得到一个 ObjectDisposedException - 它非常具有误导性(因为它没有被处理)。

是的,在对话的情况下会产生误导。编写代码是为了处理使用 Show() 显示的表单的更常见情况。一旦本机窗口被销毁,它就会沿着挂起的调用队列工作并将它们标记为完成。并将它们的“最后一个异常引发”状态设置为 ObjectDisposedException(参考源代码段中的 entry.exception)。它明确地这样做是为了防止任何调用的代码在本机窗口消失的情况下运行,这样的代码通常会因 ODE 而死。它只是跳枪并提早提出异常。您可能会争辩说 InvalidOperationException 更合适,但他们选择了 ODE。

这应该有效吗?也就是说,是否应该允许我在 ShowDialog 之后调用控件?

不,那行不通。需要 Handle 属性的值来确定调用哪个线程。但是 ShowDialog() 返回后是 IntPtr.Zero 。

您在这里遇到了一个极端情况,但一般策略必须始终是确保在允许表单关闭之前线程已完成或终止。有关此答案的更多信息。

于 2012-09-14T18:15:27.543 回答