在 VB.NET 中检查 .IsDisposed() 后出现错误 "Cannot access a disposed object"

Getting error "Cannot access a disposed object" after checking .IsDisposed() in VB.NET

为了在我的 VB.NET 应用程序中跟踪长期 运行 异步任务的进度,我创建了一个包含 ProgressBar 控件的小窗体。该任务通过 Invoke 更新 ProgressBar,我检查以确保该控件不是 Nothing,也不是 IsDisposed。这个想法是,如果用户不想看到它慢慢地滴答作响,可以关闭 ProgressBar 表单。

但是在测试中,我关闭了表单,然后收到错误消息“System.ObjectDisposedException:‘无法访问已处置的对象。”我的猜测是在更新发生时表单关闭了。也就是在我检查是否OK之后,还没完成。

Private Sub Btn_StoreForceStale_Click(sender As Button, e As EventArgs) Handles Btn_StoreForceStale.Click
    Dim pb As ProgressBar = GetProgressPopUp("Force Refreshing All Stale Items in Inventory")
    EnableButtons(False)
    Dim x As Task = Task.Run(Sub() RefreshPrices(True,, pb)).ContinueWith(Sub() EnableButtons(True))
End Sub

Public Sub RefreshPrices(Optional force As Boolean = False, Optional PriceAge As Integer = -1, Optional progBar As TextProgressBar = Nothing)
'Actual price refreshing Code
        If Not (progBar Is Nothing OrElse progBar.IsDisposed) Then
            progBar.Invoke(Sub() progBar.Increment(1))  '<-- Error happens here
        End If
End Sub

崩溃后我处于调试状态,我可以看到 progBar.IsDisposed 为真,因此不应发生增量。这就是为什么我相信表单关闭发生在增量过程中。

有没有办法避免这种情况? Form.Closing 事件中检查是否正在调用控件并延迟关闭直到未调用的方法?还是有其他事情发生?

谢谢!

Is there a way to avoid this?

是的。有很多方法。一种选择是使用 Progress(Of T) 来处理更新。在开始任务之前,在 UI 线程中创建 class 的实例。它将捕获当前的同步上下文并使用它,而不是尝试通过特定的 UI 对象进行调用。

另一种方法是使用变量来指示是否应报告进度。您可以处理 FormClosed 事件来设置变量。但是,如果这样做,则需要使用某种同步(例如 SyncLock)来确保进度更新仅在变量为 False 时发生,并且 UI 线程可以' 将其设置为 True 直到调用 BeginInvoke() 之后。请注意,您必须在此方法中使用BeginInvoke(),因为您需要在进行调用时保持锁定,并且如果您在这种情况下使用Invoke() ,调用会死锁。

您可能猜到了,我更喜欢 Progress(Of T) 选项。 :) 对我来说似乎没那么乱了。

最后,您的问题中没有足够的上下文来判断是否可以使用 Async and Await。但是,如果您可以将 long-running 任务重新组合为一个循环,每次迭代使用 Task.Run(),等待它然后更新进度条,恕我直言,这将是理想的方法。请注意,循环的每次迭代不需要对应于单个工作单元;您可以(并且应该,如果单个工作单元很小)分批完成工作,这样您就不会过于频繁地在工作线程和 UI 线程之间进行转换。