14

I'm developing Windows 10 Universal App in C#/Xaml, I'm using await Task.Delay(delayInMilliseconds) to suspend a method for given time. My scenario is somewhat realtime, so it's very sensitive to time changes, and I need to make sure that when i suspend a method for let's say 8 millisecods it is suspended for 8 millisecods. I have noticed that the actual time span for which ** Task.Delay ** suspends the method differes from what is passed as delay parameter, for 1 up to 30 ms, the length of "deviation" being different in each call. So, when I want to sleep for 8 milliseconds, my system sleeps for 9 to 39 milliseconds, and this completly ruins my scenario. So, my question what is the best way to substitute ** Task.Delay ** and to achieve good precision? For the time being I use this method:

 public static void Delay1(int delay)
    {
        long mt = delay * TimeSpan.TicksPerMillisecond;
        Stopwatch s = Stopwatch.StarNew();
        while (true)
        {
            if (s.Elapsed.TotalMilliseconds > delay)
            {
                return;
            }
        }
    }

but it guees it consumes a lot of resources, that is 100% of a processor's core. If an user has small number of processor's cores, this would be very insufficient.

xcoder37
  • 499
  • 6
  • 21
  • 4
    Task.Delay isn't a timer and isn't meant for precision timing. It's not only that it [uses a System.Threading.Timer](http://referencesource.microsoft.com/#mscorlib/system/threading/Tasks/Task.cs,5fb80297e082b8d6,references), but it also incurrs the cost of scheduling its continuation on a ThreadPool thread. The OS's [timer resolution is 15.6 ms](http://stackoverflow.com/questions/3744032/why-are-net-timers-limited-to-15-ms-resolution) so you are asking for a delay way out of limits – Panagiotis Kanavos Jul 31 '15 at 10:08
  • 1
    What are you trying to do? Why do you want such high accuracy? There are probably *other* ways to achieve the same thing. I can't think of **any** case where a Universal App requires kernel-level accuracy. Are you trying to control animation or sound playback? – Panagiotis Kanavos Jul 31 '15 at 10:16
  • @Panagiotis Kanavos: Yes, precisely, I'm trying to control animation, sound playback and network usage, that's why I need such accuracy. – xcoder37 Jul 31 '15 at 11:11
  • You *don't* need such accuracy, you only need to set the proper transition timings in XAML. Instead of trying to control things frame by frame, simply [set the animations and their timings](https://msdn.microsoft.com/en-us/library/windows/apps/xaml/jj649426.aspx). .NET will make sure the proper animations start executing at the proper points – Panagiotis Kanavos Jul 31 '15 at 11:14
  • Read to the bottom to see the amazing solution that xcoder37 finally came up with: a timer that does not consume 100% cpu time and is accurate to less than half a millisecond over any time span. When I need more accuracy I use this for the majority of my timed wait, and spin the last millisecond or so, checking a stopwatch. Perfect!! – Craig.Feied Mar 09 '18 at 02:36

5 Answers5

6

According to msdn it's not possible to achieve more accuracy due to system clock resolution:

This method depends on the system clock. This means that the time delay will approximately equal the resolution of the system clock if the millisecondsDelay argument is less than the resolution of the system clock, which is approximately 15 milliseconds on Windows systems.

w.b
  • 11,026
  • 5
  • 30
  • 49
  • Would you recommend any highr-resolution accurate substitute for this method? System.Diagnostics.Stopwatch for example is based on ** QueryPerformanceCounter ** API (https://msdn.microsoft.com/en-us/library/windows/desktop/dn553408%28v=vs.85%29.aspx), but the only solution I came up with so far is poling the Stopwatch in loop, which is indeed accurate, but heavy on resources – xcoder37 Jul 31 '15 at 10:36
  • MSDN is wrong; there are many ways to achieve better accuracy, just not with the exposed system timers or things that depend on them. – Craig.Feied Mar 09 '18 at 02:38
  • Indeed. You can set the resolution (almost) any which way you like. See https://learn.microsoft.com/en-us/windows/win32/api/timeapi/nf-timeapi-timebeginperiod (but do read the remarks!). I have not tested if it influences `Task.Delay`, though it should. – Abel Oct 26 '22 at 03:04
3

Seems like I've found a sattisfactory solution: using ManualResetEvent(false).WaitOne(delay) instead of await Task.Delay(delayInMilliseconds).

I've used following code to test both methods:

async void Probe()
    {
        for (int i = 0; i < 1000; i++)
        {
            // METHOD 1
            await Task.Delay(3); 
           // METHOD 2
           new System.Threading.ManualResetEvent(false).WaitOne(3); 
         }
    }

The code above should take exactly 3 seconds to execute. With METHOD 2 it took around 3300 ms, so the error is 0.3 ms per call. Acceptable for me. But the METHOD 1 took around 15 seconds (as mentioned by others and explained above) to execute which gives totally unacceptable error for my scenario.

EDIT: WaitOne() is a blocking call, so you'll probably want to run it as a task to get it off the UI thread (or any other thread with a message pump). @Abel has pointed out another high-res timer approach that is already baked into a task and will run in an alternate thread as shown here: (https://stackoverflow.com/a/62588903/111575). That approach makes cpu-intensive calls to Thread.Spinwait() and Thread.Sleep(0) for small intervals.

Craig.Feied
  • 2,617
  • 2
  • 16
  • 25
xcoder37
  • 499
  • 6
  • 21
  • Hm, that's interesting. Both should use the same timing facility in the kernel. Did you really not change anything else such as raising the timer frequency? The Chrome browser increases it to 1ms for example. – usr Jul 31 '15 at 11:09
  • 2
    I can't reproduce this. This will not work reliably. – usr Jul 31 '15 at 11:11
  • This artificial test says nothing about how this would work in an animation scenario as you mentioned in comments. Instead of trying to handle each individual frame or sound packet, use animations and transitions to define when each animation should start, end, etc. – Panagiotis Kanavos Jul 31 '15 at 11:17
  • BTW, using animations is *much* better for battery life and/or heat generation. – Panagiotis Kanavos Jul 31 '15 at 11:24
  • Whenever in XAML I extensively use storyboards, and transitions etc. and I think really they are great. I'm rather not the type who likes reinventing the wheel, but this case is not typical: I cooperate through XAML's SwapChainPanel component with C++/DirectX component which in addition to XAML does rendering on its own, and I have to synchronize them too. Unfortunatelly I can't go too much in app's details here. – xcoder37 Jul 31 '15 at 11:36
  • 0.3ms per call is a pretty misleading assertion. You measured that 1000 calls drifted off by 300ms (10%). Yes you can say something about the average, but average is not really what you are after. You want to know how much each individual call was off by. And that is stil unknown. It could be that some of them were off by 100% and some were only off by <1% (That's actually what I observed in my tests). Method 1 does not block the thread (GUI thread in this case) while Method 2 does. You don't want to block GUI thread for obvious reasons so basically Method 2 is a no go here. – ILIA BROUDNO Dec 05 '16 at 21:11
  • 1
    This has been a hugely useful find for me. It is reliable on Win7 and Win10. System.Threading.ManualResetEvent().WaitOne() *has* to be accurate because thread communications would fall apart if it had 16 msec of inaccuracy. My tests show that at any duration it is always accurate to less than 1 msec, often within 0.02 msec, and averages slightly better than 0.3 msec per call no matter how long the wait. This is an amazing discovery, since there is nothing else this accurate exposed in .net. I use it on a dedicated thread, and it does not block the UI. Kudos to @xcoder37! Boo to naysayers! – Craig.Feied Mar 09 '18 at 02:33
  • I came here with the same problem and your solution worked for me too so I am confirming for the benefit of others who may doubt whether this works or not that I’ve found this solution to work successfully for me too. – Professor of programming Jun 06 '22 at 12:00
  • @Craig.Feied the biggest problem with `WaitOne` is that it blocks the thread. It says so, in the first line of the MSDN docs and tooltip info. In that way, it is similar to `Thread.Sleep`. The idea behind `Task.Delay` is that is _non-blocking_. It may work in your scenario, but if you run this on the display thread, user input will be blocked. – Abel Oct 12 '22 at 21:37
  • 1
    @Abel Naturally it would be ill-advised to block any message pump thread, including the UI thread. You'll notice I said that **I use it on a dedicated thread, so it does not block the UI.** I have used this trick extensively and it is quite reliable. I use it to run 11+ channels of scrolling displays showing simultaneous waveform data streamed in from external sensors, where an accuracy of 1 msec is desired. Until I found this technique, the universal story I heard was that ~16 msec was the best achievable wait accuracy on Windows. Seven years later, this is still one of the best finds on SO! – Craig.Feied Oct 21 '22 at 20:54
  • @Craig.Feied thanks for the extra insights (and indeed I didn’t read carefully). I see you updated the answer, thanks! Interestingly, you can set the timer accuracy, which I didn’t know about. Though it also affects the thread scheduler of that process. Timers set to APC (unmanaged through api) could, in theory, use reliable (?) very short intervals. – Abel Oct 26 '22 at 01:27
  • Hmm, @Craig.Feied, while I didn't test this myself, any `Wait` function appears to be dependent on the timer resolution. While `Task.Delay` may, perhaps, always have 15.6ms accuracy (unclear from docs), [this suggests that for any `Wait`, it depends on what your process has set for timer resolution (through `timeBeginPeriod`)](https://learn.microsoft.com/en-us/windows/win32/sync/wait-functions#wait-functions-and-time-out-intervals). In other words, if some function in your app sets the resolution, it'd explain why you have such a high accuracy. – Abel Oct 26 '22 at 02:53
  • Correction: [prior to Win10 v2014 timer setting was system-wide. Since v2014, if you call the `timeBeginPeriod` function](https://learn.microsoft.com/en-us/windows/win32/api/timeapi/nf-timeapi-timebeginperiod#remarks), it is set to whatever the highest anyone has set, otherwise, it is not guaranteed. And in Win11, if your program isn't a foreground program, you're not guaranteed anything, regardless whether you call it. – Abel Oct 26 '22 at 03:03
1

You should use multi-media timers. Those are much accurate. Take a look here: https://social.msdn.microsoft.com/Forums/vstudio/en-US/2024e360-d45f-42a1-b818-da40f7d4264c/accurate-timer

ZDS Alpha
  • 39
  • 6
  • This won't help because Task.Delay already uses these timers. The method suggested for multimedia timers is obsolete and replaced with CreateTimerQueueTimer. Task.Delay uses a System.Threading.Timer internally which ... uses CreateTimerQueueTimer. – Panagiotis Kanavos Jul 31 '15 at 10:14
  • Take a look on my answer. The error is only 3 to 4 microseconds. Here: http://s23.postimg.org/qf4ke0dgr/solution.png – ZDS Alpha Aug 03 '15 at 11:33
  • @ZDSAlpha do you have a copy of that link, preferably not an image, but the source code? The link is dead. – Abel Oct 26 '22 at 01:08
1

After working hard I found a solution to sleep the thread for specific time and the error is just 3 to 4 microseconds on my Core 2 Duo CPU 3.00 GHz. Here it is:enter image description here

Here is code.(C# code is also given at the end.) Use "ThreadSleepHandler":

Public Class ThreadSleepHandler
Private Stopwatch As New Stopwatch
Private SpinStopwatch As New Stopwatch
Private Average As New TimeSpan
Private Spinner As Threading.SpinWait
Public Sub Sleep(Time As TimeSpan)
    Stopwatch.Restart()
    If Average.Ticks = 0 Then Average = GetSpinTime()
    While Stopwatch.Elapsed < Time
        If Stopwatch.Elapsed + Average < Time Then
            Average = TimeSpan.FromTicks((Average + GetSpinTime()).Ticks / 2)
        End If
    End While
End Sub
Public Function GetSpinTime() As TimeSpan
    SpinStopwatch.Restart()
    Spinner.SpinOnce()
    SpinStopwatch.Stop()
    Return SpinStopwatch.Elapsed
End Function
End Class

Here is example code:

Sub Main()
    Dim handler As New ThreadSleepHandler
    Dim stopwatch As New Stopwatch
    Do
        stopwatch.Restart()
        handler.Sleep(TimeSpan.FromSeconds(1))
        stopwatch.Stop()
        Console.WriteLine(stopwatch.Elapsed)
    Loop
End Sub

For c# programmers here is code (I have converted this code but I am not sure):

static class Main
{
public static void Main()
{
    ThreadSleepHandler handler = new ThreadSleepHandler();
    Stopwatch stopwatch = new Stopwatch();
    do {
        stopwatch.Restart();
        handler.Sleep(TimeSpan.FromSeconds(1));
        stopwatch.Stop();
        Console.WriteLine(stopwatch.Elapsed);
    } while (true);
}
}
public class ThreadSleepHandler
{
private Stopwatch Stopwatch = new Stopwatch();
private Stopwatch SpinStopwatch = new Stopwatch();
private TimeSpan Average = new TimeSpan();
private Threading.SpinWait Spinner;
public void Sleep(TimeSpan Time)
{
    Stopwatch.Restart();
    if (Average.Ticks == 0)
        Average = GetSpinTime();
    while (Stopwatch.Elapsed < Time) {
        if (Stopwatch.Elapsed + Average < Time) {
            Average = TimeSpan.FromTicks((Average + GetSpinTime()).Ticks / 2);
        }
    }
}
public TimeSpan GetSpinTime()
{
    SpinStopwatch.Restart();
    Spinner.SpinOnce();
    SpinStopwatch.Stop();
    return SpinStopwatch.Elapsed;
}
}

Note: "ThreadSleepHandler" is thread unsafe. You cannot use a single "ThreadSleepHandler" for multiple threads.

The first sleep time will not be enough accurate.

ZDS Alpha
  • 39
  • 6
  • Unfortunately, this doesn't answer the question at all. The OP wanted to control *animations* by executing multiple actions per second. Your code puts the thread to sleep for an entire second. At the 1 sec scale, the difference between a Timer or freezing and thawing the thread isn't really significant – Panagiotis Kanavos Aug 03 '15 at 12:12
  • So he needs to create a timer that should raise event at accurate time? – ZDS Alpha Aug 03 '15 at 13:42
  • 2
    Your solution polls the Stopwatch in loop which is heavy on CPU. I checked in resources monitor and CPU's usage increases noticably when I put thread to sleep using this method. In fact, your solution comes down to method Delay1() which is in my code snipped in my question. – xcoder37 Aug 04 '15 at 11:30
  • 1
    "ThreadSleepHandler" spins CPU to decrease CPU usage but it enters in loop (without spin) when the thread pointer is about to exit "Sleep()". This loop starts "one spin time" (or 8 to 10 milliseconds) before time to leave . "ThreadSleepHandler" cannot be used for sleeping thread for short time. I bet you cannot achieve more accuracy. I have implemented this to make a such accurate time. You can check it at the end of the discussion: https://social.msdn.microsoft.com/Forums/vstudio/en-US/bd479f33-8187-4815-bf87-f989a466a608/ – ZDS Alpha Aug 04 '15 at 13:36
  • 2
    The whole point of using `Task.Delay` is to free up resources, not too use them more, like in an endless loop – Alex from Jitbit Oct 09 '21 at 15:41
0

In my tests, I found that DispatcherTimer at 20ms intervals will deliver sufficiently smooth animation if you must do it in code. Doing it in XAML is another alternative as already mentioned.

ILIA BROUDNO
  • 1,539
  • 17
  • 24