尚未处理时调用 Form 的 Invoke 时出现 ObjectDisposeException

2024-05-20

我们得到一个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。

来自源.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. 为什么我会收到 ObjectDisposeException——它非常具有误导性(因为它没有被释放)。就好像代码期望只有在对象被处置时句柄才会被销毁(这正是我所期望的)。

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

Notes:

  1. 做第二个.ShowDialog()导致创建一个新句柄。

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

  3. 如果做完之后.ShowDialog()我访问Handle属性,然后做一个Invoke,就会成功。


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

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

为什么我会收到 ObjectDisposeException——它非常具有误导性(因为它没有被释放)。

是的,如果是对话,它会产生误导。编写该代码是为了处理使用 Show() 显示的表单的更常见情况。一旦本机窗口被销毁,它就会沿着待处理的调用队列进行操作并将其标记为完成。并将其“最后引发的异常”状态设置为 ObjectDisposeException(参考源代码片段中的entry.exception)。它显式地执行此操作是为了防止任何调用的代码在本机窗口消失时运行,此类代码通常会因 ODE 而终止。它只是提前提出异常。您可能会说 InvalidOperationException 更合适,但他们选择了 ODE。

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

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

您在这里遇到了一个极端情况,但一般策略必须始终是确保线程在允许表单关闭之前完成或终止。更多相关内容请参见这个答案 https://stackoverflow.com/questions/1731384/how-to-stop-backgroundworker-on-forms-closing-event/1732361#1732361.

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

尚未处理时调用 Form 的 Invoke 时出现 ObjectDisposeException 的相关文章

随机推荐