以下代码:
实际间隔始终为1014.01 ms而不是1000 ms ......
我还尝试在C ++中使用System.Windows.Forms.Timer,System.Threading.Timer和WinAPI Sleep(int)函数,但总是存在14.01 ms的额外增加。
Windows 8的系统时钟是准确的,但Windows API的.NET计时器和Sleep(int)函数都不准确。
public partial class Form1 : Form
{
private long ticks;
public Form1()
{
InitializeComponent();
}
private void Form1_Load(object sender, EventArgs e)
{
System.Timers.Timer timer = new System.Timers.Timer(1000);
// The actual interval is always 1014.01 ms ...
// I've also tried to use System.Windows.Forms.Timer, System.Threading.Timer
// and the WinAPI Sleep(int) function in C++, but the additional increase
// of 14.01 ms always exists.
timer.Elapsed += timer_Elapsed;
timer.Start();
ticks = System.DateTime.Now.Ticks;
}
void timer_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
{
textBox1.Text = Math.Round((e.SignalTime.Ticks - ticks) / 10000.0, 2).ToString();
ticks = e.SignalTime.Ticks;
}
}
更新
// Call SleepEx with bAlertable = FALSE
VOID WINAPI Kernel32.Sleep(IN DWORD dwMilliseconds)
// Call NtDelayExecution with Alertable = bAlertable
// and DelayInterval.QuadPart = dwMilliseconds * -10,000
DWORD WINAPI Kernel32.SleepEx(IN DWORD dwMilliseconds, IN BOOL bAlertable)
// The syscall stub - call the kernel mode function NtDelayExecution directly
NTSTATUS NTAPI Ntdll.NtDelayExecution(IN BOOLEAN Alertable, IN PLARGE_INTEGER DelayInterval)
// Check for the access permissions of DelayInterval and then call KeDelayExecutionThread
NTSYSCALLAPI NTSTATUS NTAPI Ntoskrnl.NtDelayExecution(IN BOOLEAN Alertable, IN PLARGE_INTEGER DelayInterval)
// Core implement of the sleep/delay function
NTKERNELAPI NTSTATUS NTAPI Ntoskrnl.KeDelayExecutionThread(IN KPROCESSOR_MODE WaitMode, IN BOOLEAN Alertable,
IN PLARGE_INTEGER Interval OPTIONAL)
{
PKTIMER Timer;
PKWAIT_BLOCK TimerBlock;
PKTHREAD Thread = KeGetCurrentThread();
NTSTATUS WaitStatus;
BOOLEAN Swappable;
PLARGE_INTEGER OriginalDueTime;
LARGE_INTEGER DueTime, NewDueTime, InterruptTime;
ULONG Hand = 0;
/* If this is a user-mode wait of 0 seconds, yield execution */
if (!(Interval->QuadPart) && (WaitMode != KernelMode))
{
/* Make sure the wait isn't alertable or interrupting an APC */
if (!(Alertable) && !(Thread->ApcState.UserApcPending))
{
/* Yield execution */
NtYieldExecution();
}
}
/* Setup the original time and timer/wait blocks */
OriginalDueTime = Interval;
Timer = &Thread->Timer;
TimerBlock = &Thread->WaitBlock[TIMER_WAIT_BLOCK];
/* Check if the lock is already held */
if (!Thread->WaitNext) goto WaitStart;
/* Otherwise, we already have the lock, so initialize the wait */
Thread->WaitNext = FALSE;
KxDelayThreadWait();
/* Start wait loop */
for (;;)
{
/* Disable pre-emption */
Thread->Preempted = FALSE;
/* Check if a kernel APC is pending and we're below APC_LEVEL */
if ((Thread->ApcState.KernelApcPending) && !(Thread->SpecialApcDisable) &&
(Thread->WaitIrql < APC_LEVEL))
{
/* Unlock the dispatcher */
KiReleaseDispatcherLock(Thread->WaitIrql);
}
else
{
/* Check if we have to bail out due to an alerted state */
WaitStatus = KiCheckAlertability(Thread, Alertable, WaitMode);
if (WaitStatus != STATUS_WAIT_0) break;
/* Check if the timer expired */
InterruptTime.QuadPart = KeQueryInterruptTime();
if ((ULONGLONG)InterruptTime.QuadPart >= Timer->DueTime.QuadPart)
{
/* It did, so we don't need to wait */
goto NoWait;
}
/* It didn't, so activate it */
Timer->Header.Inserted = TRUE;
/* Handle Kernel Queues */
if (Thread->Queue) KiActivateWaiterQueue(Thread->Queue);
/* Setup the wait information */
Thread->State = Waiting;
/* Add the thread to the wait list */
KiAddThreadToWaitList(Thread, Swappable);
/* Insert the timer and swap the thread */
ASSERT(Thread->WaitIrql <= DISPATCH_LEVEL);
KiSetThreadSwapBusy(Thread);
KxInsertTimer(Timer, Hand);
WaitStatus = (NTSTATUS)KiSwapThread(Thread, KeGetCurrentPrcb());
/* Check if were swapped ok */
if (WaitStatus != STATUS_KERNEL_APC)
{
/* This is a good thing */
if (WaitStatus == STATUS_TIMEOUT) WaitStatus = STATUS_SUCCESS;
/* Return Status */
return WaitStatus;
}
/* Recalculate due times */
Interval = KiRecalculateDueTime(OriginalDueTime,
&DueTime,
&NewDueTime);
}
WaitStart:
/* Setup a new wait */
Thread->WaitIrql = KeRaiseIrqlToSynchLevel();
KxDelayThreadWait();
KiAcquireDispatcherLockAtDpcLevel();
}
/* We're done! */
KiReleaseDispatcherLock(Thread->WaitIrql);
return WaitStatus;
NoWait:
/* There was nothing to wait for. Did we have a wait interval? */
if (!Interval->QuadPart)
{
/* Unlock the dispatcher and do a yield */
KiReleaseDispatcherLock(Thread->WaitIrql);
return NtYieldExecution();
}
/* Unlock the dispatcher and adjust the quantum for a no-wait */
KiReleaseDispatcherLockFromDpcLevel();
KiAdjustQuantumThread(Thread);
return STATUS_SUCCESS;
}
// Note that the Windows API Sleep(0) will also call NtYieldExecution(), refer to
// the function Ntoskrnl.KeDelayExecutionThread above
for (; ; )
{
Stopwatch sw = Stopwatch.StartNew();
// Thread.Sleep(1); // between 36000 and 39000
// Thread.Sleep(0); // 2 or 3
Thread.Yield(); // 1 or 2
// empty statement // always 0
Console.WriteLine(sw.ElapsedTicks);
sw.Restart();
}
static Stopwatch() {
bool succeeded = SafeNativeMethods.QueryPerformanceFrequency(out Frequency);
if(!succeeded) {
IsHighResolution = false;
Frequency = TicksPerSecond;
tickFrequency = 1;
}
else {
IsHighResolution = true;
tickFrequency = TicksPerSecond;
tickFrequency /= Frequency;
}
}
public static long GetTimestamp() {
if(IsHighResolution) {
long timestamp = 0;
SafeNativeMethods.QueryPerformanceCounter(out timestamp);
return timestamp;
}
else {
return DateTime.UtcNow.Ticks;
}
}
// Stopwatch is extremely exact without Thread.Sleep, always 1000.00 ms
// But the combination of Stopwatch + Thread.Sleep(1000) is inexact
// Stopwatch is very exact with Thread.Sleep + a spin check, always 1000 ms
thread = new Thread(() =>
{
var setText = new Action<long>(t => textBox1.Text
= Math.Round(t * 1000.0 / Stopwatch.Frequency, 2).ToString());
var sw = Stopwatch.StartNew();
for (; ; )
{
// In most cases 986 is exact enough, but very rarely it might produce
// a "1001", so use 985 here
Thread.Sleep(985);
while (sw.ElapsedTicks < Stopwatch.Frequency)
// Use Sleep(0) instead of Yield() or empty statement
Thread.Sleep(0);
// The actual interval is always 1000 ms instead of 1014.01 ms
// The Invoke method must be used since InvokeRequired is true
Invoke(setText, sw.ElapsedTicks);
sw.Restart();
}
});
thread.Start();
// DateTime.UtcNow.Ticks and DateTime.Now.Ticks are both inexact with
// Thread.Sleep + a spin check, still 1014.01 ms
thread = new Thread(() =>
{
var setText = new Action<long>(t => textBox1.Text
= Math.Round((t - ticks) / 10000.0, 2).ToString());
for (; ; )
{
Thread.Sleep(985);
while (DateTime.UtcNow.Ticks < ticks + 10000000)
Thread.Sleep(0);
var t = DateTime.UtcNow.Ticks;
Invoke(setText, t);
ticks = t;
}
});
thread.Start();
// Environment.TickCount is inexact with Thread.Sleep + a spin check,
// still 1014 ms (int value)
thread = new Thread(() =>
{
var setText = new Action<int>(t => textBox1.Text
= (t - msecs).ToString());
for (; ; )
{
Thread.Sleep(985);
while (Environment.TickCount < msecs + 1000)
Thread.Sleep(0);
var t = Environment.TickCount;
Invoke(setText, t);
msecs = t;
}
});
thread.Start();
private void Form1_FormClosed(object sender, FormClosedEventArgs e)
{
thread.Abort();
}
参考文献:
ReactOS的源代码
.NET 4.5 Update 1的官方参考资料
Shared Source CLI 2.0(用于本机功能)
SwitchToThread/Thread.Yield vs. Thread.Sleep(0) vs. Thead.Sleep(1)
感谢大家的帮助!
答案 0 :(得分:1)
睡眠会导致操作系统在时间到期之前不安排线程。请注意,计划!=运行。
调度只会将线程添加到队列中,以便最终运行,但并不总是立即运行。例如,如果已经有线程在运行,您仍需要等待其时间片完成。如果队列中有更高优先级的线程,那么它们也可以在它之前运行。
你永远不要指望Sleep()完全持续你给它的时间 - 只有至少这段时间。
定时器基本上以相同的方式运行,但在等待安排时不会阻塞线程。
此外,您应该使用Environment.TickCount
或Stopwatch
来衡量经过的时间,而不是DateTime
,这会受到系统时间变化的影响。
答案 1 :(得分:1)
您可以拨打timeBeginPeriod
来加强计时器分辨率。这也会影响GetTickCount
。
请参阅Why does increasing timer resolution via timeBeginPeriod impact power consumption?,了解您可能不想这样做的原因(当然不知道这是否会引起您的关注)。
答案 2 :(得分:1)
为什么不使用秒表? 这非常精确 MSDN Stopwatch
答案 3 :(得分:1)
如果您需要实时操作系统,则需要查看Windows桌面操作系统以外的其他位置。
答案 4 :(得分:0)
您不应该依赖定时器/休眠间隔进行时间敏感的计算 - 它永远不会精确。您可以使用Ticks或其他高精度技术。根据{{3}},Windows 7上的Ticks
分辨率为1毫秒。
另请参阅此处了解更多信息:this answer
答案 5 :(得分:0)
您的关键短语是“多媒体计时器”。
答案 6 :(得分:0)
Windows操作系统根本不适用于此类操作。对于支持上下文切换的任何OS来说,这都是一个小缺点如果您需要非常精确的计时,则需要使用嵌入式系统或旨在以这种方式运行的操作系统。
有些方法肯定会提高您尝试制作的任何行为的时间准确性,但最多也是不可靠的。在一天结束时,操作系统可以自由地强制进行上下文切换,这可能会在任何时候延迟您的计时器。
维基百科有关于该主题的更多信息:http://en.wikipedia.org/wiki/Real-time_operating_system