在我当前的项目中有一个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属性 - 这对我不起作用。所以我想我做错了。
所以我的问题: 为什么上面发布的代码会触发异常,即使我正在检查我正在访问的对象是否被处理?
答案 0 :(得分:10)
有两种解决方法:要么吞下异常并诅咒Microsoft没有包含TryInvoke
和TryBeginInvoke
方法,要么使用锁定以确保不会尝试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();
如果计时器尚未停止,则启动一个线程等待它完成,然后再次尝试关闭该表单。