解决问题”;无法访问已释放的对象";例外

本文关键字:对象 quot 例外 释放 访问 解决问题 | 更新日期: 2023-09-27 18:27:25

在我当前的项目中,有一个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事件中处理计时器对象,以及重写Dispose(bool)并在那里处理它,不幸的是,这两种方法都没有任何帮助。此外,异常并不总是被抛出,据说只有当程序退出时计时器碰巧触发时才会抛出。这种情况仍然经常发生。

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

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

解决问题”;无法访问已释放的对象";例外

有两种解决方法:要么接受异常并诅咒Microsoft没有包含TryInvokeTryBeginInvoke方法,要么使用锁定来确保在使用对象时不会尝试Dispose,在Dispose进行时不会尝试使用对象。我认为接受异常可能会更好,但有些人对这种事情有发自内心的反应,使用锁定可以从一开始就避免出现异常。

一个问题是,在调用Invoke之前,您正在对计时器线程进行检查。存在一种可能的竞争条件,在这种情况下,可以在检查之后和执行调用的操作之前处理Form。

您应该在Invoke调用的方法(本例中为lambda表达式)内部进行检查。

另一个可能的问题是您正在定时器线程上访问Cursor.Position。我不确定这是否有效——我会在主线程上这样做。您的代码还包括注释//some code,因此您可能已经省略了一些同样需要检查的代码。

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

如果您感兴趣,下面是我对您的异常的解决方案:

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

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

创建两个名为"StopTimer"answers"TimerStoped"的布尔值,它们的初始状态设置为false。将计时器的"自动重置"属性设置为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();

如果计时器尚未停止,则会启动一个线程,等待它停止,然后再次尝试关闭窗体。

相关文章: