gpt4 book ai didi

C#/.NET Timers 和 Win32 Sleep 函数都不准确

转载 作者:可可西里 更新时间:2023-11-01 11:20:25 25 4
gpt4 key购买 nike

对于下面的代码:

实际间隔始终是 1014.01 毫秒而不是 1000 毫秒 ...

我也尝试过在 C++ 中使用 System.Windows.Forms.Timer、System.Threading.Timer 和 WinAPI Sleep(int) 函数,但额外增加的 14.01 ms 始终存在。

Windows 8 的系统时钟是准确的,但 .NET 计时器和 Windows API 的 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;
}
}

更新:

  • native sleep 功能 (ReactOS):
// 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
  • .NET Sleep(1)、Sleep(0)、Yield() 和空语句的超时:
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();
}
  • 秒表依赖于 WinAPI 函数 QueryPerformanceCounter 和 QueryPerformanceFrequency:
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;
}
}
  • 秒表是准确的,但 DateTime.UtcNow.Ticks 和 Environment.TickCount 都不准确:
// 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 官方引用源

共享源 CLI 2.0(用于 native 功能)

SwitchToThread/Thread.Yield vs. Thread.Sleep(0) vs. Thead.Sleep(1)

感谢大家的帮助!

最佳答案

sleep 导致操作系统在时间到之前不调度线程。请注意,schedule != run。

调度只会将线程添加到队列中,因此它最终会运行,但并不总是立即运行。例如,如果已经有一个线程在运行,您仍然需要等待它的时间片完成。如果队列中有更高优先级的线程,它们也可以在它之前运行。

你永远不应该指望 Sleep() 会持续你给它的时间——只有至少那个时间。

计时器基本上以相同的方式运行,但在等待调度时不会阻塞线程。

此外,您应该使用 Environment.TickCountStopwatch 来测量耗时,而不是 DateTime,后者会受到系统时间。

关于C#/.NET Timers 和 Win32 Sleep 函数都不准确,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/13662345/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com