2012-03-12 94 views
12

在我目前的项目有一个表单类,它看起来是这样的:解决问题“无法访问处置的对象”。例外

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,但是我仍然对标题中显示的异常仍然感兴趣。它在UpdateDistanceLabel方法中的Invoke调用中被抛出。令我困惑的是它说“不能访问处置对象:FormMain”,即使我正在检查它是否被处置。所以这不应该发生。我也尝试在FormClosing事件中处理Timer对象,并重写Dispose(bool)并将其放置在那里,这两者都不幸根本没有任何帮助。此外,异常并不总是被抛出,据推测,只有当计时器在程序退出时触发时才会被抛出。它仍然发生很多。

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

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

回答

9

有两种解决方法:要么吞下异常,并诅咒微软没有包括一个TryInvokeTryBeginInvoke方法,或者使用锁定,以确保在它的使用不尝试Dispose制造的物体,并没有尝试使用该对象,而Dispose正在进行中。我认为吞下例外情况可能会更好,但有些人对此类事情有内心的反应,使用锁定可以避免发生例外情况。

9

一个问题是您在调用Invoke之前正在对计时器线程进行检查。有一种可能的竞争条件,表单可以在检查之后和被调用的动作执行之前被处置。

你应该在Invoke所调用的方法(在你的情况下为lambda表达式)内进行检查。

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

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

+0

谢谢。这就是我现在所做的,但是,这并不会影响行为。 – haiyyu 2012-03-12 14:54:28

6

这是我的解决您的异常,如果你有兴趣:

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

.Stop()不.DoEvents()是不够的,因为它会释放对象,而不必等待你的线程来完成其工作。

+0

是的,快速和肮脏,但它会做的伎俩。这显示了他的根本原因。 – 2012-03-12 15:23:54

+0

调用'Application.DoEvents'不能保证你避免了潜在的竞争条件,尽管它可能会使它不太可能。 – Joe 2012-03-12 16:09:18

+0

他也可以添加Thread.Sleep(100); .DoEvents()之后,但它更加脏:/ – 2012-03-12 16:51:19

0

创建两个名为'StopTimer'和'TimerStopped'的布尔值,并将它们的初始状态设置为false。将计时器的AutoReset属性设置为false。然后经过格式化的方法为以下几点:

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(); 

如果计时器还没有停止,一个线程启动等待,直到它已经这样做了,然后尝试再次关闭的形式。