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

时间:2012-03-12 14:38:37

标签: c# winforms exception dispose

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

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

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

4 个答案:

答案 0 :(得分:10)

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

答案 1 :(得分:9)

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

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

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

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

答案 2 :(得分:6)

如果您有兴趣,以下是我的异常解决方案:

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

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

答案 3 :(得分:0)

创建两个名为“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();

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