核心.NET代码引发的ObjectDisposedException异常

4

我正在处理一个现场应用程序的问题。

(不幸的是,这是事后调试 - 我只有这个堆栈跟踪。我个人从未见过这种情况,也无法再现)。

我遇到了这个异常:

message=Cannot access a disposed object.
Object name: 'Button'.
exceptionMessage=Cannot access a disposed object.
Object name: 'Button'.
exceptionDetails=System.ObjectDisposedException: Cannot access a disposed object.
Object name: 'Button'.
   at System.Windows.Forms.Control.CreateHandle()
   at System.Windows.Forms.Control.get_Handle()
   at System.Windows.Forms.Control.PointToScreen(Point p)
   at System.Windows.Forms.Button.OnMouseUp(MouseEventArgs mevent)
   at System.Windows.Forms.Control.WmMouseUp(Message& m, MouseButtons button, Int32 clicks)
   at System.Windows.Forms.Control.WndProc(Message& m)
   at System.Windows.Forms.ButtonBase.WndProc(Message& m)
   at System.Windows.Forms.Button.WndProc(Message& m)
   at System.Windows.Forms.Control.ControlNativeWindow.OnMessage(Message& m)
   at System.Windows.Forms.Control.ControlNativeWindow.WndProc(Message& m)
   at System.Windows.Forms.NativeWindow.Callback(IntPtr hWnd, Int32 msg, IntPtr wparam, IntPtr lparam)
exceptionSource=System.Windows.Forms
exceptionTargetSite=Void CreateHandle()

看起来在表单被处理后,鼠标事件到达了表单。
请注意,这个堆栈跟踪中没有我的代码。
我唯一做的奇怪的事情是,在使用ShowModal()时,我往往会相当积极地Dispose()表单(见下文"附言")。
编辑:为了澄清,我使用C++-CLI,所以实际上我不调用Dispose(),而是使用delete运算符。这与调用Dispose()相同。
但我只在ShowModal()返回后才这样做(这应该是安全的,对吧?),而且只有当我完成表单后才这样做。
我认为我读过事件可能会在事件队列中排队,但我不能相信这会是问题。我的意思是,框架肯定必须容忍旧消息吧?我可以想象在压力下,消息可能会积压,而窗口可能随时消失?
有什么想法吗?
如果您甚至能提出复制的方法,那可能会有用。
约翰
附言:
说实话,我从来没有完全理解在Form.ShowDialog()之后调用Dispose()是否是绝对必要的——对我来说,ShowDialog()的MSDN文档有点模棱两可。

你是否在进行一些后台处理,使用Invoke方法调用UI线程,无意中尝试以某种方式访问按钮? - casperOne
2
可以这样做。不幸的是,我不确定它发生在哪个表单上。我有一些可以这样做的表单。但是堆栈跟踪不是显示它是鼠标事件而不是队列调用吗? - John
你能展示一下你的清理代码吗?我曾经见过这种情况发生在对象被错误地清理或在清理后被错误地引用时。 - George Stocker
7个回答

1
我遇到了一个与我编写的Button子类相关的问题。对我而言,解决方法是在调用base.OnMouseDown和该方法中其余代码之间检查按钮的IsDisposed属性。
类似于以下内容:
protected override void OnMouseUp(System.Windows.Forms.MouseEventArgs mevent)
{
    base.OnMouseUp(mevent);

    if (this.IsDisposed) {
        return;
    }
}

1

如果您在释放表单后显示它,则会发生这种情况。(我试过了)

调用 ShowDialog 后,应该释放表单,但仅当您不打算再使用该实例时才这样做。


不,那不是这样的。正如我所说,只有在我处理完表单后才会调用Dispose()方法。 - John
你确定吗?如果你这样做,就会发生这种情况。在释放窗体后,你是否调用了控件上的InvokeText方法? - SLaks
我确定我没有在同一个线程上使用它。Invoke是可能的(请参见主要评论中casperOne的评论)。但如果它是Invoke,为什么它会出现在堆栈跟踪中作为Mouse事件? - John

1

这是一个非常奇怪的调用堆栈。按钮被处理,它的PointToScreen()方法正在重新创建句柄。但如果它被处理了,就不应该收到鼠标弹起消息。只有线程可以真正解释这一点。

此外,在鼠标弹起消息到达之前,什么都不应该被处理。据推测,这是对话框上的按钮,用于关闭它。确保使用Click事件,而不是MouseDown事件。还要确保通过分配其DialogResult属性来关闭对话框,而不是调用Close()。在C++/CLI中很尴尬,因为它不会为类型和变量保留单独的符号表。

询问用户在该机器上运行了哪种“增强功能”。


同意这很奇怪。当你说“只有线程可以真正解释这个问题”时,能详细说明一下吗?另外,为什么你说不应该调用Close()函数? - John
在调用栈的底部,按钮有一个句柄,在顶部则没有。只是试图想出其他情况,对话框应该始终通过设置其DialogResult属性来关闭,而不是调用Close方法。 - Hans Passant
我仍然不确定为什么你不应该调用Close()。MSDN似乎表明这是可以的。以下是在Form :: Close()上所说的内容:“当(1)...;和(2)您使用ShowDialog显示窗体时,窗体未在关闭时被处理的两种情况。在这些情况下,您需要手动调用Dispose来标记所有窗体控件以进行垃圾回收。”对我来说,这表明调用Close()并不会有害。你能指引我去一些地方解释为什么在这种情况下Close()是不好的吗?谢谢! - John
PS:我喜欢使用Close()的原因是,这意味着如果您将窗体用作模态或非模态窗体,则代码将正常工作。PPS:以防不清楚,我只从窗口事件中使用Close(),而不是从其他线程或类似的东西中使用它。 - John
奇怪。调用Close()会处理对话框,您不能再安全地检索对话框结果了。您是否忽略了ShowDialog()的返回值?不确定您在做什么。 - Hans Passant

1

我在诊断我的一个应用程序中类似的奇怪堆栈跟踪时发现了你的问题。以下是我必须处理的堆栈跟踪:

System.ObjectDisposedException: Cannot access a disposed object.
Object name: 'TextBox'.
  at System.Windows.Forms.Control.CreateHandle()
  at System.Windows.Forms.TextBoxBase.CreateHandle()
  at System.Windows.Forms.Control.get_Handle()
  at System.Windows.Forms.Control.set_CaptureInternal(Boolean value)
  at System.Windows.Forms.Control.WmMouseDown(Message& m, MouseButtons button, Int32 clicks)
  at System.Windows.Forms.Control.WndProc(Message& m)
  at System.Windows.Forms.TextBoxBase.WndProc(Message& m)
  at System.Windows.Forms.TextBox.WndProc(Message& m)
  at System.Windows.Forms.Control.ControlNativeWindow.OnMessage(Message& m)
  at System.Windows.Forms.Control.ControlNativeWindow.WndProc(Message& m)
  at System.Windows.Forms.NativeWindow.Callback(IntPtr hWnd, Int32 msg, IntPtr wparam, IntPtr lparam)

虽然我的问题不同于你的,但它具有一些相同的关键特征:

  • 句柄存在于堆栈跟踪的开头,但不存在于结尾。
  • 在堆栈跟踪中看不到我的任何代码。

我认为我的问题与你的不同,但我想分享一下我发现的内容,希望它能给你一些启示。经过一些实验,我成功地重现了我的问题。我已经钩住了另一个控件的LostFocus事件,在某些情况下,LostFocus事件处理程序会删除某些不再相关的控件。

然而,如果LostFocus事件是由于用户单击要删除的控件之一而触发的,则会出现上述堆栈跟踪。在我的情况下,Control.WndProc调用Control.WmKillFocus,最终调用我的LostFocus事件处理程序(不同的控件),我处理了被单击的控件,然后Control.WmMouseDown被调用。

你是否有类似的情况发生,在WmMouseUp之前触发了某些东西?使用.NET Reflector查看在WmMouseUp之前可能调用哪些事件可以帮助你追踪问题。


0
看起来一个鼠标事件在表单被处理后到达了该表单。
确实是这样!异常清楚地说明“无法访问已释放的对象,对象名称:button”,这意味着已经释放的按钮再次面临释放的压力,因此出现了异常。因此,根据您提供的堆栈跟踪,如果您观察下面的行,就可以明显地看到在按钮的OnMouseUp事件期间对已释放的按钮进行了第二次重复释放。
  at System.Windows.Forms.Button.OnMouseUp(MouseEventArgs mevent)

我建议创建一个自定义按钮,继承System.Windows.Forms.Button控件,并重写OnMouseUp事件,在其中通过Button的IsDisposed属性防止重复的dispose。
public class FlatSylteSystemButton : System.Windows.Forms.Button
{
    public FlatStyleSystemButton()
    {
           this.FlatStyle =FlatStyle.System;
    }

    protected override void OnMouseUp(MouseEventArgs mevent)
    {
        if(!this.IsDisposed)
        {
           base.OnMouseUp(mevent);
        }
    }
}

希望这能帮到你!


0
为什么不在using语句中使用表单实例化?这将避免调用dispose的需要,并确保在正确的时间完成。
例如(未经测试,现在无法访问编译器)
using(FormX frm = new FormX())
{
   DialogResult res = frm.ShowDialog();
   // Do your other stuff after
}

实际上我正在使用C++/CLI,所以没有"using"关键字。但是,在我处理后我肯定不会再使用该表单。不过,这是个好观点。 - John
哦,好的,我被 C# 标签误导了;-) - Shimrod

0

我在单击非模态表单中的按钮时调用Close()方法时遇到了相同的问题。调试winforms程序集使我发现了按钮的FlatStyle属性。你有button.FlatSyle = FlatStyle.System吗?

由于某些原因,有时按钮不接收WM_KILLFOCUS(OnLostFocus)消息,并在窗体关闭和释放期间出现ObjectDisposedException。如果你设置了button.FlatSyle = FlatStyle.System,这可能会导致此类堆栈跟踪异常。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接