项目里面用到的这些看起来名字高大上的定时器测试下来也是非常不准。看了源码发现也是用System.Timers.Timer或者用的是Thread休眠的方式来实现的。100毫秒就不准了。直到一番搜索,发现利用多媒体定时器winmm.dll的MillisecondTimer是可用的。原文来自博客(dehai)Timer计时不准确的问题及解决方法”。代码如下:
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;
}
}
}
测试代码
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准好多倍啊
加上了些注释,并针对项目进行了封装:
/// <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);
}
}