如何在.Net中正确实现自定义计时器

Posted

技术标签:

【中文标题】如何在.Net中正确实现自定义计时器【英文标题】:How to correctly implement a custom timer in .Net 【发布时间】:2011-11-13 04:49:21 【问题描述】:

我需要一个具有以下行为的计时器:

毫秒精度 我希望仅在当前刻度处理程序完成后才调用刻度事件处理程序(很像 winforms 计时器) 我希望主 UI 线程上的异常不会被线程计时器吞没,因此这需要 Invoke/Send 而不是 BeginInvoke/Post

我玩过CreateTimerQueueTimer 并取得了一些成功,但同时在删除计时器时遇到了代码重入和/或锁定问题。

我决定创建自己的计时器,以便更好地了解引擎盖下发生的事情,从而解决锁定和重入问题。我的代码似乎运行良好,让我相信我也可以使用它。看起来好听吗?

我检查了定时器是否被删除,以确保删除完成后才能再次创建定时器。看起来还可以吗?

注意:我应该说我调用timeBeginPeriod(1)timeEndPeriod(1)是为了达到毫秒精度。

(以下代码是从vb.net转换成c#的,如有遗漏请见谅

ETA:我发现它有问题。如果计时器以 1 毫秒的间隔运行,并且我调用 Change(300),它会锁定 @while (this.DeleteRequest)。这 一定是因为TimerLoopthis.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()


【问题讨论】:

你可以尝试在Code Review发帖 谢谢,我没听说过那个网站。 “毫秒精度”是什么意思?调用 Sleep 将确保您的线程至少在指定的时间内不会再次被调度,但不能保证它何时会被调度。 我的意思是,如果我指定 7 毫秒的间隔,那么这就是我将收到的给定或花费一毫秒的时间。诸如 system.threading.timer 之类的计时器不提供这种精度级别。此外,调用 timeBeginPeriod(1) 似乎确实提供了准确的睡眠。 你知道timeBeginPeriod 有系统范围的副作用并增加功耗(但我不知道多少)? 【参考方案1】:

我建议使用 p/Invoke 并使用 Win32 的计时器队列中的计时器:

http://msdn.microsoft.com/en-us/library/ms686796(v=vs.85).aspx

应该注意,托管 CLR 环境内置了很多不确定性,例如垃圾收集。仅仅因为您的计时器有 1 毫秒的周期并不意味着一定会发生这种情况。

此外,文档没有提到它,但计时器调用的回调必须通过GCHandle 或其他构造固定在内存中,而不是垃圾回收。当一个计时器(或多个计时器,如果你杀死一个计时器队列),回调将最后一次执行。不确定是内部等待到期,还是内部事件句柄发信号。

DeleteTimerQueueTimer()DeleteTimerQueueEx() 的执行可以同步进行,因此在所有计时器发出信号并调用其最后一个回调之前它们不会返回,但这样做不是最理想的。

如果您不固定回调并防止它们被垃圾收集,那么事情将会顺利进行......大多数时候。您会遇到随机异常。

此外,如果定时器被删除,回调应该足够聪明,可以退出,以免它引用已经被 GC 处理的东西。

【讨论】:

【参考方案2】:

μTimer 会是一个更好的例子!

你可以在这里找到它@https://***.com/questions/15725711/obtaining-microsecond-precision-using-net-without-platform-invoke?noredirect=1#comment22341931_15725711

它可以提供低至 1µs 的准确等待时间,并且可能更短,具体取决于您的 NIC!

如果您还需要什么,请告诉我!

【讨论】:

注意:链接失效了。

以上是关于如何在.Net中正确实现自定义计时器的主要内容,如果未能解决你的问题,请参考以下文章

如何在 PHP + MySQL 中正确实现自定义会话持久化器?

在 Rails 4 中正确实现 Parsley.js 自定义远程验证器

如何在iOS7中正确定位后退按钮

如何在python的类中正确实现辅助函数

如何在 models.py 文件中正确定义 ManyToMany 字段

如何在struct中正确定义一个函数指针,它以struct为指针?