是否有一个高分辨率计时器,每次计时器经过时都会引发一个事件,就像System.Timer
上课一样?Elapse
我需要一个每毫秒的高分辨率计时器。
我不断遇到解释秒表可以测量高分辨率的帖子,但我不想测量时间,我想创建 1 毫秒的间隔。
.NET 中有什么东西还是我要编写自己的高分辨率计时器?
是否有一个高分辨率计时器,每次计时器经过时都会引发一个事件,就像System.Timer
上课一样?Elapse
我需要一个每毫秒的高分辨率计时器。
我不断遇到解释秒表可以测量高分辨率的帖子,但我不想测量时间,我想创建 1 毫秒的间隔。
.NET 中有什么东西还是我要编写自己的高分辨率计时器?
我所知道的 .NET 框架中没有内置任何内容。Windows 通过Multimedia Timer API提供了一种用于高分辨率计时器事件的机制。下面是我制作的一个快速示例,它似乎可以完成这项工作。这里似乎也有一个很好的例子。
我会注意到这个 API 会改变系统范围的设置,这会降低系统性能,所以买家要小心。出于测试目的,我建议跟踪计时器触发的频率,以验证计时是否与您尝试模拟的设备相似。由于 Windows 不是实时操作系统,系统上的负载可能会导致 MM 计时器延迟,从而导致 100 毫秒的间隔,其中包含 100 个快速连续的事件,而不是 100 个间隔 1 毫秒的事件。一些关于 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 文章https://www.codeproject.com/Articles/17474/Timer-surprises-and-how-to-avoid-创建了一个围绕 Windows 多媒体计时器的基本包装器他们
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();
}
}
输出看起来像这样
2021-11-19 更新:TimerEventHandler
根据 chris 的评论添加班级成员。
有一个选项:使用Thread.Sleep(0)
. 尝试调用Thread.Sleep(1)
或使用 aSystem.Threading.Timer
将始终归结为系统计时器分辨率。取决于一个可能不是最好的主意,在一天结束时,您的应用程序可能只是不允许timeBeginPeriod(...)
从 winmm.dll 调用。
以下代码在我的开发机器 (i7q) 上可以解析到 +/- 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
}
有关更全面的实施,请参阅我的其他答案
https://github.com/HypsyNZ/Precision-Timer.NET
一个高精度 .NET 计时器,它不会杀死您的 CPU 或收集垃圾。
它的设计与任何其他 .NET 计时器一样易于使用。
使用 Precision Timer 的方式与使用任何其他 Windows Timer 的方式相同。
private PrecisionTimer PT;
public PrecisionTimer SetInterval(Action TimerTask, int Interval)
{
PT = new PrecisionTimer();
PT.Tick += (sender, args) => { TimerTask(); };
PT.AutoReset = true;
PT.Period = Interval;
PT.Resolution = 0;
PT.Start();
return PT;
}
PT.Tick += (sender, args) => { TimerTask(); };
Interval 已过时将触发的 TimerEventCallback
PT.Period = Interval;
获取或设置Tick Events
, in之间的时间Milliseconds
。
PT.Resolution = 0;
分辨率以毫秒为单位。
Resolution
值越小越大。
分辨率为 0 表示周期性事件应以尽可能高的精度发生。
但是,为了减少系统开销,您应该使用适合您的应用程序的最大值。
.Net 定时器的正常分辨率约为 12-15 毫秒,但这取决于您的主板高精度事件定时器 (HPET) 和可编程中断控制器 (PIC)
PT.AutoReset = true;
True
如果每次间隔过去时都Precision Timer
应该引发事件。Ticks
False
如果在Precision Timer
第一次间隔过去后应该只引发一次事件。
Precision Timer.NET 是一个多媒体计时器,您可以在MSDN上阅读有关多媒体计时器的更多信息
尝试创建新的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();