ShowTimerEventFiredDelegate允许ShowTimerEventFired方法在UI线程上调用它自己Figure 显示了发生这一切的代码
通过查询InvokeRequired属性可以非常容易的知道你是否从当前线程可以安全的访问Windows窗体控件在这个例子中如果列表框的InvokeRequired属性为真窗体的BeginInvoke方法就可以被ShowTimerEventFired方法调用然后再被ShowTimerEventFiredDelegate方法调用这能够保证列表框的Add方法在UI线程上执行
正如你所看到的当你编写异步定时器事件时有许多问题需要意识到在使用SystemTimersTimer和SystemThreadingTimer之前我推荐你阅读Ian Griffith的文章Windows Forms:Give Your NETbased Application a Fast and Responsive UI with Multiple Threads 该文刊登在MSDN杂志的年月份的期刊上
处理定时器事件重入
当和异步定时器事件打交道时如由SystemTimersTimer和SystemThreadingTimer产生的定时器事件有另外一个细微之处你需要考虑问题就是必须处理代码重入如果你的定时器事件处理函数代码执行时间比你的定时器引发定时器事件的时间间隔要长你预先又没有采取必要的措施保护防止多线程访问你的对象和变量你就会陷入调试的困境看一下下面的代码片断
private int tickCounter = ;
private void tmrTimersTimer_Elapsed(object sender SystemTimersElapsedEventArgse)
{
SystemThreadingInterlockedIncrement(ref tickCounter);
ThreadSleep();
MessageBoxShow(tickCounterToString());
}
假设你的定时器间隔属性设置为毫秒你也许会奇怪当第一个信息框弹出时显示的值是这是因为在这秒期间第一个定时器事件正在睡眠而定时器却在不同的工作者线程上继续产生时间消失事件因此在第一个定时器事件处理完成之前tickCounter变量被增加了次注意我使用了InterlockedIncrement方法以线程安全的方式增加tickCounter变量的值也有其它方法可以这样做但是InterlockIncrement是为这种操作而特别设计的
解决这种问题的简单方法就是在你的事件处理函数代码块中暂时禁止定时器接着再允许定时器就像下面的代码
private void tmrTimersTimer_Elapsed(object sender SystemTimersElapsedEventArgse)
{
tmrTimersEnabled = false;
SystemThreadingInterlockedIncrement(ref tickCounter);
ThreadSleep();
MessageBoxShow(tickCounterToString());
tmrTimersTimerEnabled = true;
}
有了这段代码消息框就会每秒钟显示一次就像你所期望的那样tickCounter的值每次只增加另外一些可选的原始同步对象就是Monitor或mutex去确保所有将来的事件被排队直到当前的事件处理函数执行完成
结论
为了快速方便的看到NET框架中这三个定时器类的不同之处见Figure 对三个类的比较当使用定时器类时有一点你要考虑的就是是否以使用Windows调度器去定期的运行标准的可执行程序来更简单的解决问题
[] [] [] []