如何在.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)时锁定@。这一定是因为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()
}

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

我建议使用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,并可能更低,这取决于您的网卡!

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