1

次のコードの場合:

実際の間隔は、1000ミリ秒ではなく常に1014.01ミリ秒です...

また、C ++でSystem.Windows.Forms.Timer、System.Threading.Timer、およびWinAPI Sleep(int)関数を使用しようとしましたが、14.01ミリ秒の追加の増加が常に存在します。

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

アップデート:

  • ネイティブスリープ機能(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.5Update1の公式リファレンスソース

シェアードソースCLI2.0(ネイティブ機能用)

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

助けてくれたみんなに感謝します!

4

7 に答える 7

1

スリープにより、OS は時間切れになるまでスレッドをスケジュールしません。スケジュール != 実行に注意してください。

スケジューリングは、スレッドをキューに追加するだけなので、最終的には実行されますが、すぐには実行されません。たとえば、既に実行中のスレッドがある場合でも、そのタイム スライスが終了するまで待機する必要があります。優先順位の高いスレッドがキューにある場合、それらのスレッドもその前に実行される可能性があります。

Sleep() が指定した時間だけ持続することを期待するべきではありません。少なくともその時間だけです。

タイマーは基本的に同じように動作しますが、スケジュールされるのを待っている間、スレッドをブロックしません。

また、システム時間の変更の影響を受ける ではなく、Environment.TickCountまたはを使用Stopwatchして経過時間を測定する必要があります。DateTime

于 2012-12-01T18:29:17.977 に答える
1

呼び出しtimeBeginPeriodて、タイマーの解像度を引き締めることができます。これは にも影響しGetTickCountます。

timeBeginPeriod を使用してタイマーの分解能を上げると、電力消費に影響を与えるのはなぜですか?を参照してください。なぜこれをしたくないのかについての議論のために(もちろん、これがあなたの状況で懸念されるかどうかはわかりません).

于 2012-12-01T18:29:39.427 に答える
1

リアルタイム オペレーティング システムが必要な場合は、Windows デスクトップ OS 以外の場所を探す必要があります。

例:リアルタイム オペレーティング システムのリスト

于 2012-12-01T18:45:48.863 に答える
1

なぜストップウォッチを使わないのですか?それは非常に正確 です MSDN ストップウォッチ

于 2012-12-01T18:35:58.433 に答える
0

Windows OSは、そのようなことを目的として設計されたものではありません。これは、コンテキストスイッチングをサポートするOSのわずかな欠点です。非常に正確なタイミングが必要な場合は、このように動作するように設計された組み込みシステムまたはOSを使用する必要があります。

生成しようとしている動作のタイミング精度を確実に向上させる方法がありますが、せいぜい信頼性が低くなります。1日の終わりに、オペレーティングシステムは、いつでもタイマーを遅らせる可能性のあるコンテキストスイッチを自由に強制できます。

ウィキペディアには、このテーマに関する詳細情報があります:http: //en.wikipedia.org/wiki/Real-time_operating_system

于 2012-12-01T19:50:39.307 に答える
0

時間に敏感な計算のためにタイマー/スリープ間隔に頼るべきではありません - それは決して正確ではありません. 代わりにティックまたは他の高精度技術を使用できます。この回答Ticksによると、Windows 7 での解像度は 1 ミリ秒です。

詳細については、こちらも参照してください:正確な 10 進タイマーを作成するには?

于 2012-12-01T18:28:04.570 に答える
0

あなたのキーワードは「マルチメディア タイマー」です。

于 2012-12-01T18:31:00.893 に答える