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

时间:2011-09-08 17:19:45

标签: c# .net vb.net timer

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

  • 毫秒精度
  • 我希望只在当前的tick处理程序完成后调用tick事件处理程序(很像winforms计时器)
  • 我希望主要UI线程上的异常不会被线程计时器吞没,所以这需要调用/发送而不是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()

}

2 个答案:

答案 0 :(得分:1)

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

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

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

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

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

如果你没有固定回调并防止它们被垃圾收集,那么事情就会在很多时候发生。你会遇到随机异常。

此外,如果正在删除计时器,回调应该足够智能以挽救,以免它引用已经GC的东西。

答案 1 :(得分:0)

μTimer是一个更好的例子!

你可以在@ https://stackoverflow.com/questions/15725711/obtaining-microsecond-precision-using-net-without-platform-invoke?noredirect=1#comment22341931_15725711

找到它

它提供精确的等待时间,最短1μs,可能更低,具体取决于您的NIC!

如果您还有其他需要,请告诉我们!