是否有一个高分辨率计时器可以在定时器到期时引发事件,就像System.Timer
类一样?我需要一个高分辨率计时器,以每1毫秒触发一次Elapse
事件。
我一直看到一些文章解释说Stopwatch可以测量高分辨率,但是我不想测量时间,我想创建一个每1毫秒的间隔。
.NET中是否有相应的东西或者我需要编写自己的高分辨率计时器?
是否有一个高分辨率计时器可以在定时器到期时引发事件,就像System.Timer
类一样?我需要一个高分辨率计时器,以每1毫秒触发一次Elapse
事件。
我一直看到一些文章解释说Stopwatch可以测量高分辨率,但是我不想测量时间,我想创建一个每1毫秒的间隔。
.NET中是否有相应的东西或者我需要编写自己的高分辨率计时器?
据我所知,.NET框架中没有内置此功能。Windows通过Multimedia Timer API提供了高分辨率的定时器事件机制。下面是我快速编写的一个示例,看起来可以胜任工作。这里还有一个不错的示例。
需要注意的是,此API更改系统范围的设置可能会降低系统性能,请谨慎使用。建议在测试过程中跟踪定时器触发的频率,以验证定时与要模拟的设备的计时相似。由于Windows不是实时操作系统,您系统上的负载可能会导致MM定时器延迟,从而在100毫秒的间隔中包含100个快速连续的事件,而不是间隔1毫秒的100个事件。关于MM定时器的一些更多阅读材料。
class Program
{
static void Main(string[] args)
{
TestThreadingTimer();
TestMultimediaTimer();
}
private static void TestMultimediaTimer()
{
Stopwatch s = new Stopwatch();
using (var timer = new MultimediaTimer() { Interval = 1 })
{
timer.Elapsed += (o, e) => Console.WriteLine(s.ElapsedMilliseconds);
s.Start();
timer.Start();
Console.ReadKey();
timer.Stop();
}
}
private static void TestThreadingTimer()
{
Stopwatch s = new Stopwatch();
using (var timer = new Timer(o => Console.WriteLine(s.ElapsedMilliseconds), null, 0, 1))
{
s.Start();
Console.ReadKey();
}
}
}
public class MultimediaTimer : IDisposable
{
private bool disposed = false;
private int interval, resolution;
private UInt32 timerId;
// Hold the timer callback to prevent garbage collection.
private readonly MultimediaTimerCallback Callback;
public MultimediaTimer()
{
Callback = new MultimediaTimerCallback(TimerCallbackMethod);
Resolution = 5;
Interval = 10;
}
~MultimediaTimer()
{
Dispose(false);
}
public int Interval
{
get
{
return interval;
}
set
{
CheckDisposed();
if (value < 0)
throw new ArgumentOutOfRangeException("value");
interval = value;
if (Resolution > Interval)
Resolution = value;
}
}
// Note minimum resolution is 0, meaning highest possible resolution.
public int Resolution
{
get
{
return resolution;
}
set
{
CheckDisposed();
if (value < 0)
throw new ArgumentOutOfRangeException("value");
resolution = value;
}
}
public bool IsRunning
{
get { return timerId != 0; }
}
public void Start()
{
CheckDisposed();
if (IsRunning)
throw new InvalidOperationException("Timer is already running");
// Event type = 0, one off event
// Event type = 1, periodic event
UInt32 userCtx = 0;
timerId = NativeMethods.TimeSetEvent((uint)Interval, (uint)Resolution, Callback, ref userCtx, 1);
if (timerId == 0)
{
int error = Marshal.GetLastWin32Error();
throw new Win32Exception(error);
}
}
public void Stop()
{
CheckDisposed();
if (!IsRunning)
throw new InvalidOperationException("Timer has not been started");
StopInternal();
}
private void StopInternal()
{
NativeMethods.TimeKillEvent(timerId);
timerId = 0;
}
public event EventHandler Elapsed;
public void Dispose()
{
Dispose(true);
}
private void TimerCallbackMethod(uint id, uint msg, ref uint userCtx, uint rsv1, uint rsv2)
{
var handler = Elapsed;
if (handler != null)
{
handler(this, EventArgs.Empty);
}
}
private void CheckDisposed()
{
if (disposed)
throw new ObjectDisposedException("MultimediaTimer");
}
private void Dispose(bool disposing)
{
if (disposed)
return;
disposed = true;
if (IsRunning)
{
StopInternal();
}
if (disposing)
{
Elapsed = null;
GC.SuppressFinalize(this);
}
}
}
internal delegate void MultimediaTimerCallback(UInt32 id, UInt32 msg, ref UInt32 userCtx, UInt32 rsv1, UInt32 rsv2);
internal static class NativeMethods
{
[DllImport("winmm.dll", SetLastError = true, EntryPoint = "timeSetEvent")]
internal static extern UInt32 TimeSetEvent(UInt32 msDelay, UInt32 msResolution, MultimediaTimerCallback callback, ref UInt32 userCtx, UInt32 eventType);
[DllImport("winmm.dll", SetLastError = true, EntryPoint = "timeKillEvent")]
internal static extern void TimeKillEvent(UInt32 uTimerId);
}
我无法让Mike的解决方案运行起来,因此根据这篇codeproject文章,创建了一个基本包装器,基于Windows多媒体计时器实现。 https://www.codeproject.com/Articles/17474/Timer-surprises-and-how-to-avoid-them
public class WinMMWrapper
{
[DllImport("WinMM.dll", SetLastError = true)]
public static extern uint timeSetEvent(int msDelay, int msResolution,
TimerEventHandler handler, ref int userCtx, int eventType);
public delegate void TimerEventHandler(uint id, uint msg, ref int userCtx,
int rsv1, int rsv2);
public enum TimerEventType
{
OneTime = 0,
Repeating = 1
}
private readonly Action _elapsedAction;
private readonly int _elapsedMs;
private readonly int _resolutionMs;
private readonly TimerEventType _timerEventType;
private readonly TimerEventHandler _timerEventHandler;
public WinMMWrapper(int elapsedMs, int resolutionMs, TimerEventType timerEventType, Action elapsedAction)
{
_elapsedMs = elapsedMs;
_resolutionMs = resolutionMs;
_timerEventType = timerEventType;
_elapsedAction = elapsedAction;
_timerEventHandler = TickHandler;
}
public uint StartElapsedTimer()
{
var myData = 1; //dummy data
return timeSetEvent(_elapsedMs, _resolutionMs / 10, _timerEventHandler, ref myData, (int)_timerEventType);
}
private void TickHandler(uint id, uint msg, ref int userctx, int rsv1, int rsv2)
{
_elapsedAction();
}
}
以下是如何使用它的示例
class Program
{
static void Main(string[] args)
{
var timer = new WinMMWrapper(100, 25, WinMMWrapper.TimerEventType.Repeating, () =>
{
Console.WriteLine($"Timer elapsed {DateTime.UtcNow:o}");
});
timer.StartElapsedTimer();
Console.ReadKey();
}
}
TimerEventHandler
在WinMMWrapper中被垃圾回收了。可以通过将处理程序设置为类级别变量并将其传递到timeSetEvent
来解决这个问题。我猜这是一个常见的非托管代码问题。 - chris84948Thread.Sleep(0)
。调用 Thread.Sleep(1)
或使用 System.Threading.Timer
始终会受到系统计时器分辨率的影响。依赖其中之一可能不是最好的选择,在一天结束时,您的应用程序可能无法从 winmm.dll 调用 timeBeginPeriod(...)
。
以下代码可以在我的开发机器上解决到 +/- 10ns (0.10ms) ,但可能更高。它会在一个 CPU 核心上产生稳定的负载,将其使用率提高到 100%。没有实际的操作系统减速会发生,该代码通过尽早调用 Thread.Sleep 来放弃大部分 CPU 时间量:
var requiredDelayMs = 0.1;
var sw = new System.Diagnostics.Stopwatch();
sw.Start();
while (true)
{
if (sw.Elapsed.TotalMilliseconds >= requiredDelayMs)
{
// call your timer routine
}
Thread.Sleep(0); // setting at least 1 here would involve a timer which we don't want to
}
如需更全面的实现,请参见我的其他答案
Thread.Yield()
会降低上下文切换的机会。 Thread.Sleep(0)
会导致上下文切换到优先级较低的进程。提高当前进程的优先级将减少Thread.Yield()
本身上下文切换的机会。 - user2864740https://github.com/HypsyNZ/Precision-Timer.NET https://www.nuget.org/packages/PrecisionTimer.NET/
一个高精度的.NET计时器,不会占用你的CPU或被垃圾回收。System.Threading.Thread
并使用 System.Threading.Thread.Sleep
。var thrd = new Syatem.Threading.Thread(() => {
while (true) {
// do something
System.Threading.Thread.Sleep(1); // wait 1 ms
}
});
thrd.Start();