在 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 线程之间进行转换。
为了在我的 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 线程之间进行转换。