1

For the following code:

The actual interval is always 1014.01 ms rather than 1000 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.

The System Clock of Windows 8 is exact, but both the .NET timers and the Sleep(int) function of Windows API are inexact.

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;
    }
}

Update:

  • The native Sleep function (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
  • The timeouts of the .NET Sleep(1), Sleep(0), Yield() and empty statement:
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();
}
  • The Stopwatch depends on the WinAPI functions QueryPerformanceCounter and 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; 
    }
}
  • The Stopwatch is exact, but neither DateTime.UtcNow.Ticks nor Environment.TickCount is exact:
// 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();
}

References:

Source code of the ReactOS

Official Reference Source of the .NET 4.5 Update 1

The Shared Source CLI 2.0 (for native functions)

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

Thanks to everyone for help!

Community
  • 1
  • 1
kitii
  • 13
  • 4

7 Answers7

1

Sleep causes the OS to not schedule the thread until the time is up. Note that schedule != run.

Scheduling only adds the thread to a queue so it'll get run eventually, but not always immediately. For instance, if there's already a thread running, you still need to wait for its time slice to finish. If there are higher-priority threads in the queue, those could also run before it.

You should never count on Sleep() lasting exactly the amount of time you give it -- only at least that amount of time.

Timers basically operate the same way, but don't block a thread while they're waiting to be scheduled.

Also, you should be using Environment.TickCount or Stopwatch to measure elapsed time, not DateTime, which is affected by changes to the system time.

Cory Nelson
  • 29,236
  • 5
  • 72
  • 110
  • According to [this answer](http://stackoverflow.com/a/8865560/897326), Environment.TickCount is not better than DateTime.Ticks, and in fact worse in some cases, precision-wise. – Victor Zakharov Dec 01 '12 at 18:37
  • `Environment.TickCount` can have less precision, but is made specifically for measuring elapsed wall time, which `DateTime` is not. If `Environment.TickCount` doesn't have enough precision, use `Stopwatch`. – Cory Nelson Dec 01 '12 at 20:54
  • Thank you, @CoryNelson! - Stopwatch is extremely exact since it depends on the WinAPI functions QueryPerformanceCounter and QueryPerformanceFrequency. My solution is Stopwatch + Thread.Sleep + a spin check, it works excellently - the CPU usage is always 0% ... – kitii Dec 06 '12 at 16:54
  • You're mostly correct -- `Stopwatch` actually has a bug (marked as wont-fix) on Windows 9x, CE, and Windows Phone 7 where it incorrectly uses `DateTime`. Fixed in WP8, where the full Windows kernel is used instead of CE. – Cory Nelson Dec 06 '12 at 17:04
1

You can call timeBeginPeriod to tighten up the timer resolution. This also affects GetTickCount.

See Why does increasing timer resolution via timeBeginPeriod impact power consumption? for a discussion of why you might not want to do this (no idea whether this would be a concern in your situation of course).

Community
  • 1
  • 1
Tom Seddon
  • 2,648
  • 1
  • 19
  • 28
  • Can you make your Timer fire more than 64 times per second this way? – Victor Zakharov Dec 01 '12 at 18:33
  • @Neolisk Yes (but don't forget the downside) – CodesInChaos Dec 01 '12 at 18:39
  • @CodesInChaos: Interesting, but I would not go this way because `This function affects a global Windows setting.` If it was scoped to your application, would be another story. – Victor Zakharov Dec 01 '12 at 18:43
  • Thank you, Tom Seddon! - I think timeBeginPeriod is also a solution although I haven't tried yet ... But if it will significantly cause more CPU usage, I'll prefer not to use it ... – kitii Dec 06 '12 at 16:58
1

why not use Stopwatch? it's very precise MSDN Stopwatch

Goran Štuc
  • 581
  • 4
  • 15
1

If you need a Real-time Operating System, you need to look someplace else other than a Windows desktop OS.

For example: List of real-time operating systems

Black Frog
  • 11,595
  • 1
  • 35
  • 66
0

You should not rely on Timer/Sleep interval for time sensitive calculations - it will never be exact. You can use Ticks instead or other high precision techniques. Resolution of Ticks is 1ms on Windows 7, according to this answer.

Also see here for more information: How to make an accurate decimal Timer?

Community
  • 1
  • 1
Victor Zakharov
  • 25,801
  • 18
  • 85
  • 151
  • `DateTime.UtcNow` isn't any better. It only updates every few milliseconds using the same system timer as `Thread.Sleep`, `Environment.TickCount`,... – CodesInChaos Dec 01 '12 at 18:30
  • @CodesInChaos: my point was that if interval is 1000, it will never fire exactly after 1000ms. So you need to use time elapsed to account for the extra delta. Ticks work well for this purpose. – Victor Zakharov Dec 01 '12 at 18:31
0

Your keyphrase is "multimedia timers".

0

The Windows OS is simply not designed for such things. This is a slight drawback to any OS that supports context switching. If you need very precise timings you'll need to use an embedded system or an OS that is designed to behave this way.

There are methods that will certainly improve the timing accuracy of whatever behavior you're trying to produce but it will be unreliable at best. At the end of the day the operating system is free to force a context switch which could delay your timer at any time.

Wikipedia has some more info on the subject: http://en.wikipedia.org/wiki/Real-time_operating_system

Spencer Ruport
  • 34,865
  • 12
  • 85
  • 147