如何在.net中正确实现自定义计时器
本文关键字:实现 自定义 计时器 net | 更新日期: 2023-09-27 18:07:55
我有一个定时器的要求,具有以下行为:
- 毫秒精度
- 我希望tick事件处理程序只在当前tick处理程序完成时被调用(很像winforms计时器)
- 我希望主UI线程上的异常不会被线程计时器吞噬,所以这需要调用/发送而不是BeginInvoke/Post
我玩过CreateTimerQueueTimer
并取得了一些成功,但同时在删除计时器时遇到了代码重入和/或锁的问题。
我决定创建我自己的计时器,这样我就可以更好地了解在引擎盖下发生了什么,这样我就可以修复锁定和重新进入问题。我的代码似乎工作得很好,这使我相信我也可以使用它。看起来合理吗?
我已经检查了计时器是否被删除,以确保在计时器再次创建之前删除完成。看起来可以吗?
注意:我应该说我调用timeBeginPeriod(1)
和timeEndPeriod(1)
是为了达到毫秒级的精度。
(下面的代码是从vb.net转换为c#的,因此对任何遗漏的混乱表示歉意)
ETA:我发现了一个问题。如果计时器以1毫秒的间隔运行,并且我调用Change(300)
,它会在(this.DeleteRequest
)时锁定@。这一定是因为TimerLoop
在this.CallbackDelegate.Invoke(null)
呼叫中
public class MyTimer : IDisposable
{
private System.Threading.TimerCallback CallbackDelegate;
private bool DeleteRequest;
private System.Threading.Thread MainThread;
public MyTimer(System.Threading.TimerCallback callBack)
{
this.CallbackDelegate = callBack;
}
public void Create(int interval)
{
while (this.DeleteRequest) {
System.Threading.Thread.Sleep(0);
}
if (this.MainThread != null) {
throw new Exception("");
}
this.MainThread = new System.Threading.Thread(TimerLoop);
// Make sure the thread is automatically killed when the app is closed.
this.MainThread.IsBackground = true;
this.MainThread.Start(interval);
}
public void Change(int interval)
{
// A lock required here?
if (!this.IsRunning()) {
throw new Exception("");
}
this.Delete();
this.Create(interval);
}
public void Delete()
{
this.DeleteRequest = true;
}
public bool IsRunning()
{
return (this.MainThread != null) && this.MainThread.IsAlive;
}
private void TimerLoop(object args)
{
int interval = (int)args;
Stopwatch sw = new Stopwatch();
sw.Start();
do {
if (this.DeleteRequest) {
this.MainThread = null;
this.DeleteRequest = false;
return;
}
long t1 = sw.ElapsedMilliseconds;
// I want to wait until the operation completes, so I use Invoke.
this.CallbackDelegate.Invoke(null);
if (this.DeleteRequest) {
this.MainThread = null;
this.DeleteRequest = false;
return;
}
long t2 = sw.ElapsedMilliseconds;
int temp = Convert.ToInt32(Math.Max(interval - (t2 - t1), 0));
sw.Reset();
if (temp > 0) {
System.Threading.Thread.Sleep(temp);
}
sw.Start();
} while (true);
}
// The dispose method calls this.Delete()
}
我建议使用p/Invoke并使用Win32定时器队列中的计时器:
http://msdn.microsoft.com/en-us/library/ms686796 (v = vs.85) . aspx
应该注意的是,托管CLR环境有很多内置的不确定性,例如垃圾收集。仅仅因为你的计时器的周期是1毫秒,并不意味着一定会发生这种情况。
同样,文档没有提到,但是计时器调用的回调必须固定在内存中,不能通过GCHandle
或其他结构进行垃圾收集。当一个定时器(或多个定时器,如果你终止一个定时器队列),回调将被执行最后一次。不确定是内部等待到期,还是通过内部事件句柄发出信号。
DeleteTimerQueueTimer()
和DeleteTimerQueueEx()
的执行可以同步,所以它们不会返回,直到所有计时器都发出信号并调用它们的最后一个回调,但这样做将是次优的。
如果你不固定回调并防止它们被垃圾收集,事情将会顺利进行…大多数时候。你会遇到随机的异常。
而且,回调应该足够聪明,在计时器被删除时跳出来,以免它引用已经被GC处理过的东西。
μTimer将是一个更好的例子!
你可以在这里找到@ https://stackoverflow.com/questions/15725711/obtaining-microsecond-precision-using-net-without-platform-invoke?noredirect=1#comment22341931_15725711
它提供精确的等待时间低至1µs,并可能更低,这取决于您的网卡!
如果你还需要什么,请告诉我!