无法访问已处置的对象 - 如何修复?

Posted

技术标签:

【中文标题】无法访问已处置的对象 - 如何修复?【英文标题】:Cannot access a disposed object - How to fix? 【发布时间】:2010-09-06 23:25:00 【问题描述】:

在 VB.NET WinForms 项目中,出现异常

无法访问已处置的对象

关闭表单时。它很少发生,我无法按需重新创建它。堆栈跟踪如下所示:

Cannot access a disposed object. Object name: 'dbiSchedule'.
  at System.Windows.Forms.Control.CreateHandle()
  at System.Windows.Forms.Control.get_Handle()
  at System.Windows.Forms.Control.PointToScreen(Point p)
  at Dbi.WinControl.Schedule.dbiSchedule.a(Boolean A_0)
  at Dbi.WinControl.Schedule.dbiSchedule.a(Object A_0, EventArgs A_1)
  at System.Windows.Forms.Timer.OnTick(EventArgs e)
  at System.Windows.Forms.Timer.TimerNativeWindow.WndProc(Message& m)
  at System.Windows.Forms.NativeWindow.Callback(IntPtr hWnd, Int32 msg, IntPtr wparam, IntPtr lparam)

dbiSchedule 是来自 Dbi-tech 的日程控制。表单上有一个计时器,每隔几分钟就会更新屏幕上的日程安排。

任何想法是什么导致了异常以及如何解决它?甚至只是能够按需重新创建它?


嘿嘿!感谢所有的答案。我们确实在 FormClosing 事件上停止了 Timer,并且在 Timer Tick 事件中使用它之前检查了 schedule 组件上的 IsDisposed 属性,但它没有帮助。

这是一个非常烦人的问题,因为如果有人提出了可行的解决方案 - 我将无法确认解决方案,因为我无法手动重新创建问题。

【问题讨论】:

【参考方案1】:

在访问控件之前尝试检查IsDisposed 属性。假设您正在使用 FormClosed 事件,您还可以在 FormClosing 事件中检查它。

我们确实会在 FormClosing 事件,我们会检查 计划中的 IsDisposed 属性 在 Timer 中使用之前的组件 勾选事件,但没有帮助。

在检查 IsDisposed 之前调用 GC.Collect 可能会有所帮助,但要小心。阅读 Rico Mariani 的这篇文章“When to call GC.Collect()”。

【讨论】:

【参考方案2】:

您确定计时器不会以某种方式超过“dbiSchedule”并在“dbiSchedule”被处理后触发?

如果是这种情况,如果计时器触发得更快,您可能能够更一致地重新创建它,从而增加您在计时器触发时关闭表单的机会。

【讨论】:

【参考方案3】:

查看错误堆栈跟踪,您的计时器似乎仍然处于活动状态。尝试在关闭表单时取消计时器(在表单的 OnClose() 方法中)。这看起来是最干净的解决方案。

【讨论】:

【参考方案4】:

看起来像是线程问题。假设:也许您有主线程和计时器线程访问此控件。主线程关闭 - 调用 Control.Dispose() 表示我已完成此 Control,我将不再对此进行调用。但是,计时器线程仍然处于活动状态 - 上下文切换到该线程,它可以在同一控件上调用方法。现在控制说我被处置了(已经放弃了我的资源),我将不再工作。 ObjectDisposed 异常。

如何解决这个问题:在计时器线程中,在调用控件上的方法/属性之前,请检查

if ControlObject.IsDisposed then return; // or do whatever - but don't call control methods

或在处理对象之前停止计时器线程。

【讨论】:

检查 IsDisposed 会减少,但不会消除问题。正确的解决方法是在关闭表单之前停止计时器。【参考方案5】:

另一个可以停止计时器的地方是 FormClosing 事件 - 这发生在表单实际关闭之前,因此是在事情可能访问不可用资源之前停止事情的好地方。

【讨论】:

【参考方案6】:

我们确实检查了 IsDisposed 属性 使用前的计划组件 在 Timer Tick 事件中,但它没有 帮助。

如果我理解该堆栈跟踪,那么问题不是您的计时器,而是控件本身中的一个 - 可能是他们没有正确清理。

您是否在他们的控件上明确调用 Dispose?

【讨论】:

【参考方案7】:

停止计时器并不意味着它不会再次被调用,取决于您何时停止计时器,timer_tick 可能仍会在表单的消息循环中排队。将会发生的事情是,你会得到一个你可能没有预料到的蜱虫。您可以做的是在您的 timer_tick 中,在执行 Timer_Tick 方法之前检查您的计时器的 Enabled 属性。

【讨论】:

【参考方案8】:

如果这种情况偶尔发生,那么我的猜测是它与计时器有关。

我猜测(这只是一个猜测,因为我无法访问您的代码)在表单关闭时计时器正在触发。 dbiSchedule 对象已被释放,但计时器仍然设法尝试调用它。这不应该发生,因为如果计时器有对调度对象的引用,那么垃圾收集器应该看到这个而不是处理它。

这让我问:你是在调度对象上手动调用 Dispose() 吗?如果是这样,您是否在处理计时器之前这样做?确保在 Dispose 之前释放对 schedule 对象的所有引用(即提前释放计时器)。

现在我意识到,从您发布此消息到我回答之间已经过去了几个月,所以希望您已经解决了这个问题。我写这篇文章是为了其他可能会遇到类似问题的人。

希望这会有所帮助。

【讨论】:

【参考方案9】:

我遇到了同样的问题,并使用在表单关闭时设置的布尔标志解决了它(System.Timers.Timer 没有 IsDisposed 属性)。在我启动计时器的表单上的任何地方,我都让它检查这个标志。如果已设置,则不要启动计时器。原因如下:

原因:

我在表单关闭事件中停止并处理计时器。我在 Timer_Elapsed() 事件中启动计时器。如果我在 Timer_Elapsed() 事件中间关闭表单,计时器将立即被 Form_Closing() 事件处理掉。这将发生在 Timer_Elapsed() 事件完成之前,更重要的是,在它到达这行代码之前:

_timer.Start()

一旦执行了该行,就会抛出 ObjectDisposedException() 并出现您提到的错误。

解决方案:

Private Sub myForm_FormClosing(ByVal sender As System.Object, ByVal e As System.Windows.Forms.FormClosingEventArgs) Handles MyBase.FormClosing
    ' set the form closing flag so the timer doesn't fire even after the form is closed.
    _formIsClosing = True
    _timer.Stop()
    _timer.Dispose()
End Sub

这是计时器经过的事件:

Private Sub Timer_Elapsed(ByVal sender As System.Object, ByVal e As System.Timers.ElapsedEventArgs) Handles _timer.Elapsed
    ' Don't want the timer stepping on itself (ie. the time interval elapses before the first call is done processing)
    _timer.Stop()

    ' do work here

    ' Only start the timer if the form is open. Without this check, the timer will run even if the form is closed.
    If Not _formIsClosing Then
        _timer.Interval = _refreshInterval
        _timer.Start() ' ObjectDisposedException() is thrown here unless you check the _formIsClosing flag.
    End If
End Sub

有趣的是,即使它在尝试启动计时器时会抛出 ObjectDisposedException,但即使关闭表单,计时器仍会启动,导致它运行(线程只会在应用程序关闭时停止)。

【讨论】:

【参考方案10】:

我的解决方案是尝试捕获,并且工作正常

试试 this.Invoke(new EventHandler(DoUpdate)); 赶上

【讨论】:

【参考方案11】:

因为解决方案文件夹位于 OneDrive 文件夹中。

如果您将解决方案文件夹从一个驱动器文件夹中移出,错误就会消失。

最好的

【讨论】:

以上是关于无法访问已处置的对象 - 如何修复?的主要内容,如果未能解决你的问题,请参考以下文章

无法访问已处置的对象。对象名称:'System.Net.Sockets.UdpClient'

引发线程事件时无法访问已处置的对象

无法在单元测试中访问已处置的对象

使用异步存储库模式 - 无法访问已处置的对象

通过 Azure 函数注入 DbContext 时无法访问已处置的对象

无法访问已处置的对象 VC++ VS2017 Professional