解决问题”;无法访问已释放的对象";例外
本文关键字:对象 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没有包含TryInvoke
和TryBeginInvoke
方法,要么使用锁定来确保在使用对象时不会尝试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();
如果计时器尚未停止,则会启动一个线程,等待它停止,然后再次尝试关闭窗体。