ObjectDisposedException在未调用Form时调用Form的Invoke

时间:2012-09-14 16:47:44

标签: c# winforms invoke

我们在尚未处置的表单上调用ObjectDisposedException时获得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之后调用控件吗?

  4. 注意:

    1. 执行第二次.ShowDialog()会导致创建新句柄。

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

    3. 如果在.ShowDialog()之后我访问Handle属性,然后执行Invoke,则会成功。

1 个答案:

答案 0 :(得分:4)

  

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

摧毁原生窗口并使其消失。在此之后,Handle属性将是IntPtr.Zero。

  

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

是的,如果是对话,则会产生误导。编写代码是为了处理使用Show()显示的表单的更常见情况。一旦本机窗口被销毁,它就会在挂起调用的队列中运行并将它们标记为完成。并将其“最后一个异常引发”状态设置为ObjectDisposedException(Reference Source片段中的entry.exception)。它明确地这样做是为了防止任何被调用的代码在本机窗口运行时运行,这样的代码通常会死于ODE。它只是跳起枪并提前引发异常。您可能会认为InvalidOperationException更合适,但他们选择了ODE。

  

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

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

你在这里遇到了一个问题,但总体策略必须始终是你确保在允许表单关闭之前完成或终止线程。有关this answer中的更多信息。