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

时间:2008-08-27 06:36:17

标签: .net vb.net winforms

在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的日程控制。表单上有一个计时器,每隔几分钟就会在屏幕上更新计划。

任何想法导致异常的原因以及我如何修复它?或者甚至只是能够按需重新创建它?


HeJ小鼠!感谢所有的答案。我们确实在FormClosing事件上停止了Timer,我们确实检查了调度组件上的IsDisposed属性,然后在Timer Tick事件中使用它,但它没有帮助。

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

11 个答案:

答案 0 :(得分:19)

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

  

我们确实停止了   FormClosing事件,我们检查   IsDisposed财产按计划进行   在Timer中使用它之前的组件   勾选事件,但它没有帮助。

在检查IsDisposed之前调用GC.Collect可能会有所帮助,但请注意这一点。阅读Rico Mariani撰写的这篇文章“When to call GC.Collect()”。

答案 1 :(得分:10)

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

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

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

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

答案 2 :(得分:2)

  

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

如果我理解堆栈跟踪,那么问题就不在于你的计时器,它是控制器本身的一个 - 可能是他们没有正确清理。

你明确地在他们的控制上调用Dispose吗?

答案 3 :(得分:2)

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

答案 4 :(得分:2)

我遇到了同样的问题,并使用在表单关闭时设置的布尔标志解决了它(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,计时器仍然会启动,即使表单关闭时它也会运行(线程只会在应用程序关闭时停止)

答案 5 :(得分:1)

你确定计时器不会以某种方式超过'dbiSchedule'并在'dbiSchedule'处理完毕后解雇?

如果是这种情况,如果计时器更快地触发,你可能能够更一致地重新创建它,从而增加你在计时器触发时关闭Form的几率。

答案 6 :(得分:1)

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

答案 7 :(得分:1)

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

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

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

现在我意识到你发布这个和我回答的时间已经过了几个月,所以希望你已经解决了这个问题。我写这篇文章的目的是为了后来可能会遇到类似问题的其他人的利益。

希望这有帮助。

答案 8 :(得分:1)

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

  

尝试{
  this.Invoke(new EventHandler(DoUpdate));     }
抓住{}

答案 9 :(得分:0)

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

答案 10 :(得分:-1)

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

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

最好的