解决问题“无法访问被处置对象。”例外

在我当前的项目中有一个Form类,如下所示:

public partial class FormMain : Form { System.Timers.Timer timer; Point previousLocation; double distance; public FormMain() { InitializeComponent(); distance = 0; timer = new System.Timers.Timer(50); timer.AutoReset = true; timer.Elapsed += new System.Timers.ElapsedEventHandler(timer_Elapsed); timer.Start(); } private void timer_Elapsed(object sender, System.Timers.ElapsedEventArgs e) { if (previousLocation != null) { // some code UpdateDistanceLabel(distance); UpdateSpeedLabel(v); } previousLocation = Cursor.Position; } private void UpdateDistanceLabel(double newDistance) { if (!lblDistance.IsDisposed && !IsDisposed) { Invoke(new Action(() => lblDistance.Text = String.Format("Distance: {0} pixels", newDistance))); } } private void UpdateSpeedLabel(double newSpeed) { if (!lblSpeed.IsDisposed && !IsDisposed) { Invoke(new Action(() => lblSpeed.Text = String.Format("Speed: {0} pixels per second", newSpeed))); } } } 

如您所见,我正在使用System.Timers.Timer对象。 我知道我可以使用System.Windows.Forms.Timer,但我对我仍然得到标题中显示的exception的原因很感兴趣。 它会在UpdateDistanceLabel方法的Invoke调用中抛出。 令我困惑的是它说“无法访问处置对象:FormMain”,即使我正在检查它是否被处置。 所以这不应该发生。 我也尝试在FormClosing事件中处理timer对象以及重写Dispose(bool)并将其丢弃,但遗憾的是两者都没有帮助。 此外,exception并不总是被抛出,据说只有当程序在程序退出时才会发生计时。 它仍然发生了很多。

我已经看到有关于此的大量线索,但我已经尝试过那里发布的解决方案,其中大多数涉及检查IsDisposed属性 – 这对我不起作用。 所以我想我做错了什么。

所以我的问题是:为什么上面发布的代码会触发exception,即使我正在检查我访问的对象是否被处理?

有两种解决方法:要么吞下exception并诅咒Microsoft没有包含TryInvokeTryBeginInvoke方法,要么使用锁定以确保在使用对象时不会尝试Dispose对象,并且不会尝试使用Dispose正在进行中的对象。 我认为吞下exception可能更好,但是有些人对这些事情有内心反应,并且使用锁定可以避免首先发生exception。

一个问题是您在调用Invoke之前正在检查计时器线程。 存在可能的竞争条件,其中可以在检查之后且在执行调用的动作之前处理表单。

您应该在Invoke的方法(在您的情况下为lambda表达式)中进行检查。

另一个可能的问题是您正在访问计时器线程上的Cursor.Position 。 我不确定这是否有效 – 我会在主线程上执行此操作。 您的代码还包含注释//some code – 因此您可能会省略一些您还需要检查的代码。

总的来说,使用System.Windows.Forms.Timer可能会更好。

如果您有兴趣,这是我的例外解决方案:

 private void FormMain_FormClosing(object sender, FormClosingEventArgs e) { timer.Stop(); Application.DoEvents(); } 

没有.DoEvents()的.Stop()是不够的,因为它会在不等待你的线程完成其工作的情况下处理对象。

创建两个名为“StopTimer”和“TimerStopped”的布尔值,其初始状态设置为false。 将计时器的AutoReset属性设置为false。 然后将Elapsed方法格式化为以下内容:

 Invoke((MethodInvoker)delegate { // Work to do here. }); if (!StopTimer) timer.Start(); else TimerStopped = true; 

这样就可以防止竞争条件,检查计时器是否应该继续,并在方法结束时进行报告。

现在将FormClosing方法设置为:

 if (!TimerStopped) { StopTimer = true; Thread waiter = new Thread(new ThreadStart(delegate { while (!TimerStopped) { } Invoke((MethodInvoker)delegate { Close(); }); })); waiter.Start(); e.Cancel = true; } else timer.Dispose(); 

如果计时器尚未停止,则启动一个线程等待它完成,然后再次尝试关闭该表单。