是否有高分辨率计时器在每次计时器过去时引发事件,就像System.Timer
类一样?我需要每分钟Elapse
的高分辨率计时器。
我一直在发帖说明秒表可以测量高分辨率,但我不想测量时间,我想创建一个1毫秒的间隔。
.NET中有什么东西,还是我要编写自己的高分辨率计时器?
答案 0 :(得分:11)
我所知道的.NET框架中没有任何内置功能。 Windows具有通过Multimedia Timer API进行高分辨率计时器事件的机制。下面是一个快速的例子我掀起了似乎做这个工作。似乎还有一个很好的例子here。
我会注意到此API会更改可能降低系统性能的系统范围设置,因此买家要小心。出于测试目的,我建议跟踪定时器触发的频率,以验证定时与您尝试模拟的设备类似。由于Windows不是实时操作系统,因此系统上的负载可能导致MM定时器延迟,导致100 ms的间隙快速连续包含100个事件,而不是间隔1 ms的100个事件。关于MM计时器的一些additional reading。
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);
}
答案 1 :(得分:2)
你可以拥有一个基于秒表的高分辨率计时器,它可以提供比现今系统更好的1ms分辨率。
以下是我在Stackoverflow上的另一个答案https://stackoverflow.com/a/45097518/548894
实施https://gist.github.com/DraTeots/436019368d32007284f8a12f1ba0f545
它适用于所有平台,并且只要StopWatch.IsHighPrecision == true
它的Elapsed
事件保证不重叠(这可能很重要,因为事件处理程序中的状态更改可能不受多线程访问保护)
以下是如何使用它:
Console.WriteLine($"IsHighResolution = {HighResolutionTimer.IsHighResolution}");
Console.WriteLine($"Tick time length = {HighResolutionTimer.TickLength} [ms]");
var timer = new HighResolutionTimer(0.5f);
// UseHighPriorityThread = true, sets the execution thread
// to ThreadPriority.Highest. It doesn't provide any precision gain
// in most of the cases and may do things worse for other threads.
// It is suggested to do some studies before leaving it true
timer.UseHighPriorityThread = false;
timer.Elapsed += (s, e) => { /*... e.Delay*/ }; // The call back with real delay info
timer.Start();
timer.Stop(); // by default Stop waits for thread.Join()
// which, if called not from Elapsed subscribers,
// would mean that all Elapsed subscribers
// are finished when the Stop function exits
timer.Stop(joinThread:false) // Use if you don't care and don't want to wait
这是一个基准(和实例):
https://gist.github.com/DraTeots/5f454968ae84122b526651ad2d6ef2a3
值得一提的是:
我在Ubuntu上的单声道精度相同。
在玩基准时,我看到的最大和非常罕见的偏差约为0.5毫秒 (这可能意味着什么,它不是实时系统,但仍然值得一提)
Stopwatch ticks are not TimeSpan ticks. 在该Windows 10计算机上 HighResolutionTimer.TickLength为0.23 [ns]。
答案 2 :(得分:1)
恐怕是陈旧的答案。 .net 中高分辨率计时的最终解决方案是 System.Diagnostics.Stopwatch
类。如果运行代码的系统具有高分辨率计时器硬件,则此类使用高分辨率计时器(有时精度为纳秒级)。如果不是,则返回到标准的 Windows 计时器,其精度约为 50 毫秒。
在过去十年中,几乎每台机器都配备了高分辨率计时器。
如果由于某种可怕的不幸,您不得不在极其陈旧的硬件上运行,那么上面给出的多媒体计时器解决方案可以提供毫秒级的精度(但会降低整体系统性能)。
值得注意的是,这个问题已有 6 年历史了,因此原始海报完全有可能在过时的硬件上运行。只需使用 Stopwatch
。
答案 3 :(得分:1)
我无法让 Mike 的解决方案发挥作用,并基于此代码项目文章 https://www.codeproject.com/Articles/17474/Timer-surprises-and-how-to-avoid-them
围绕 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;
public WinMMWrapper(int elapsedMs, int resolutionMs, TimerEventType timerEventType, Action elapsedAction)
{
_elapsedMs = elapsedMs;
_resolutionMs = resolutionMs;
_timerEventType = timerEventType;
_elapsedAction = elapsedAction;
}
public uint StartElapsedTimer()
{
var myData = 1; //dummy data
return timeSetEvent(_elapsedMs, _resolutionMs / 10, new TimerEventHandler(TickHandler), 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();
}
}
输出看起来像这样
答案 4 :(得分:0)
尝试创建新的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();
答案 5 :(得分:0)
有一个选项:使用Thread.Sleep(0)
。尝试调用Thread.Sleep(1)
或使用System.Threading.Timer
总是取决于系统计时器的分辨率。视情况而定不是最好的主意,最终可能是不允许您的应用程序从winmm.dll调用timeBeginPeriod(...)
。
以下代码可以在我的开发机(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
}
有关更全面的实现,请参见my other answer