Windows高速定时器,多媒体定时器winmm.dll库的使用

项目里面用到的这些看起来名字高大上的定时器测试下来也是非常不准。看了源码发现也是用System.Timers.Timer或者用的是Thread休眠的方式来实现的。100毫秒就不准了。直到一番搜索,发现利用多媒体定时器winmm.dll的MillisecondTimer是可用的。原文来自博客(dehai)Timer计时不准确的问题及解决方法”。代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
public sealed class MillisecondTimer : IComponent, IDisposable
    {
        //*****************************************************  字 段  *******************************************************************
        private static TimerCaps caps;
        private int interval;
        private bool isRunning;
        private int resolution;
        private TimerCallback timerCallback;
        private int timerID;
 
        //*****************************************************  属 性  *******************************************************************
        /// <summary>
        ///
        /// </summary>
        public int Interval
        {
            get
            {
                return this.interval;
            }
            set
            {
                if ((value < caps.periodMin) || (value > caps.periodMax))
                {
                    throw new Exception("超出计时范围!");
                }
                this.interval = value;
            }
        }
 
 
        /// <summary>
        ///
        /// </summary>
        public bool IsRunning
        {
            get
            {
                return this.isRunning;
            }
        }
 
        /// <summary>
        ///
        /// </summary>
        public ISite Site
        {
            set;
            get;
        }
 
        //*****************************************************  事 件  *******************************************************************
        public event EventHandler Disposed;  // 这个事件实现了IComponet接口
        public event EventHandler Tick;
        //***************************************************  构造函数和释构函数  ******************************************************************
 
        static MillisecondTimer()
        {
            timeGetDevCaps(ref caps, Marshal.SizeOf(caps));
        }
 
        public MillisecondTimer()
        {
            this.interval = caps.periodMin;    //
            this.resolution = caps.periodMin;  //
            this.isRunning = false;
            this.timerCallback = new TimerCallback(this.TimerEventCallback);
        }
 
        public MillisecondTimer(IContainer container)
            : this()
        {
            container.Add(this);
        }
 
        ~MillisecondTimer()
        {
            timeKillEvent(this.timerID);
        }
 
        //*****************************************************  方 法  *******************************************************************
        /// <summary>
        ///
        /// </summary>
        public void Start()
        {
            if (!isRunning)
            {
                timerID = timeSetEvent(this.interval, this.resolution, this.timerCallback, 0, 1); // 间隔性地运行
                if (timerID == 0)
                {
                    throw new Exception("无法启动计时器");
                }
                isRunning = true;
            }
        }
 
        /// <summary>
        ///
        /// </summary>
        public void Stop()
        {
            if (isRunning)
            {
                timeKillEvent(this.timerID);
                isRunning = false;
            }
        }
 
        /// <summary>
        /// 实现IDisposable接口
        /// </summary>
        public void Dispose()
        {
            timeKillEvent(this.timerID);
            GC.SuppressFinalize(this);
            Disposed?.Invoke(this, EventArgs.Empty);
        }
 
        //***************************************************  内部函数  ******************************************************************
        [DllImport("winmm.dll")]
        private static extern int timeSetEvent(int delay, int resolution, TimerCallback callback, int user, int mode);
 
        [DllImport("winmm.dll")]
        private static extern int timeKillEvent(int id);
 
        [DllImport("winmm.dll")]
        private static extern int timeGetDevCaps(ref TimerCaps caps, int sizeOfTimerCaps);
 
        private void TimerEventCallback(int id, int msg, int user, int param1, int param2)
        {
            Tick?.Invoke(this, null);  // 引发事件
        }
        //***************************************************  内部类型  ******************************************************************
 
        private delegate void TimerCallback(int id, int msg, int user, int param1, int param2); // timeSetEvent所对应的回调函数的签名
 
        /// <summary>
        /// 定时器的分辨率(resolution)。单位是ms,毫秒?
        /// </summary>
        [StructLayout(LayoutKind.Sequential)]
        private struct TimerCaps
        {
            public int periodMin;
            public int periodMax;
        }
    }
}

测试代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
class Program
   {
       static Dictionary<string, int> dtList = new Dictionary<string, int>();
       static void Main(string[] args)
       {
           //var timer = new DoubleThreadTimer(100, 100);
           //timer.OnRunningCallback += Timer1_Tick;
           //timer.Start();
 
           var timer1 = new MillisecondTimer();
           timer1.Interval = 2;
           timer1.Tick += Timer1_Tick;
           timer1.Start();
           Console.ReadLine();
       }
 
       private static void Timer1_Tick(object sender, EventArgs e)
       {
           var dt = DateTime.Now;
           string dtStr = dt.ToString("yyyyMMdd HHmmss");
           if (dtList.ContainsKey(dtStr))
           {
               dtList[dtStr]++;
               if (dtList[dtStr] % 500 == 0)
               {                   
                   Console.WriteLine(dtStr + "--->" + dtList[dtStr]);
                   dtList.Remove(dtStr);
               }
           }
           else
           {
               if (dtList.Count > 1)
               {
                   foreach (var item in dtList)
                   {
                       Console.WriteLine(item.Key + "--->" + item.Value);
                   }
                   dtList.Clear();
               }
               dtList.Add(dtStr, 1);               
           }
       }
   }

  

测试结论: 时间间隔设置为5毫秒,表现很稳定。2毫秒有时准。可靠性比.net的timer准好多倍啊

加上了些注释,并针对项目进行了封装:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
/// <summary>
    /// 用Windows多媒体定时器winmm.dll库封装的定时器
    /// </summary>
    public class MillisecondTimer : ITimer, IDisposable
    {
        #region 定时器事件类型
        const int TIME_ONESHOT = 0;
        const int TIME_PERIODIC = 1;
        #endregion
 
        private bool isRunning = false;
        /// <summary>
        /// 定时器的分辨率(resolution)。单位是毫秒
        /// </summary>
        [StructLayout(LayoutKind.Sequential)]
        private struct TimerCaps
        {
            public int periodMin;
            public int periodMax;
        }
        #region winmm.dll API声明
        /// <summary>
        /// 设置一个媒体定时器(定时器事件类型常用的有2种,0:TIME_ONESHOT,1:TIME_PERIODIC)
        /// </summary>
        /// <param name="delay">以毫秒指定事件的周期。</param>
        /// <param name="resolution">以毫秒指定延时的精度,数值越小定时器事件分辨率越高。缺省值为1ms。</param>
        /// <param name="callback">指向一个回调函数。(委托实例)</param>
        /// <param name="user">存放用户提供的回调数据。</param>
        /// <param name="mode">指定定时器事件类型:0->TIME_ONESHOT:uDelay毫秒后只产生一次事件;1->TIME_PERIODIC :每隔delay毫秒周期性地产生事件。</param>
        /// <returns>定时器的ID,释放资源的时候需要</returns>
        [DllImport("winmm.dll")]
        private static extern int timeSetEvent(int delay, int resolution, TimerCallback callback, int user, int mode);
        /// <summary>
        /// 结束定时器,释放资源
        /// </summary>
        /// <param name="id">设置定时器返回的ID</param>
        /// <returns></returns>
        [DllImport("winmm.dll")]
        private static extern int timeKillEvent(int id);
        /// <summary>
        /// 初始化结构体,TimerCaps{periodMin,periodMax}
        /// </summary>
        /// <param name="caps">TimerCaps</param>
        /// <param name="sizeOfTimerCaps">TimerCaps的长度</param>
        /// <returns></returns>
        [DllImport("winmm.dll")]
        private static extern int timeGetDevCaps(ref TimerCaps caps, int sizeOfTimerCaps);
        #endregion
        private delegate void TimerCallback(int id, int msg, int user, int param1, int param2); // timeSetEvent所对应的回调函数的签名
        public event EES.Common.ManualTimer.TimerCallback OnRunningCallback;
        public event EES.Common.ManualTimer.TimerCallback OnStartedCallback;
        public event EES.Common.ManualTimer.TimerCallback OnStopedCallback;
 
        private TimerCallback m_TimerCallback;
        private int timerID;
        private TimerCaps caps = new TimerCaps();
 
        public MillisecondTimer(int dueTime, int period)
        {
            timeGetDevCaps(ref caps, Marshal.SizeOf(caps));
            caps.periodMin = period;
            caps.periodMax = dueTime;
            isRunning = false;
            m_TimerCallback = new TimerCallback(TimerEventCallback);           
        }
 
        /// <summary>
        /// 触发事件
        /// </summary>
        /// <param name="id"></param>
        /// <param name="msg"></param>
        /// <param name="user"></param>
        /// <param name="param1"></param>
        /// <param name="param2"></param>
        private void TimerEventCallback(int id, int msg, int user, int param1, int param2)
        {
            OnRunningCallback?.Invoke(this, new EventArgs());
        }
 
        /// <summary>
        /// 启动定时器,回调OnStartedCallback
        /// </summary>
        public void Start()
        {
            if (!isRunning)
            {               
                timerID = timeSetEvent(caps.periodMin, caps.periodMin, m_TimerCallback, 0, TIME_PERIODIC); // 间隔性地运行
                GC.KeepAlive(m_TimerCallback);
                if (timerID == 0)
                {
                    throw new EESException("无法启动计时器");
                }
                isRunning = true;
                if (isRunning)
                    OnStartedCallback?.Invoke(this, EventArgs.Empty);
            }
        }
        /// <summary>
        ///停止定时器
        /// </summary>
        public void Stop()
        {
            if (isRunning)
            {
                timeKillEvent(timerID);
                isRunning = false;
                OnStopedCallback?.Invoke(this, EventArgs.Empty);
                Dispose();
            }
        }
        /// <summary>
        /// 获取定时器允许状态
        /// </summary>
        /// <returns></returns>
        public bool IsRunning()
        {
            return isRunning;
        }
        /// <summary>
        /// Dispose
        /// </summary>
        public void Dispose()
        {
            if (!timerID.Equals(0))
                timeKillEvent(timerID);
            GC.SuppressFinalize(this);
        }
    }

  

 

posted @   数据酷软件  阅读(3221)  评论(1编辑  收藏  举报
编辑推荐:
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
阅读排行:
· DeepSeek 开源周回顾「GitHub 热点速览」
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· AI与.NET技术实操系列(二):开始使用ML.NET
· 单线程的Redis速度为什么快?
点击右上角即可分享
微信分享提示