1

I've recently noticed very strange behaviour of ManualResetEvent class in .NET framework. I am using C#, VS 2015, project's target is set to 4.5.2. Here is the full code:

using System;
using System.Diagnostics;
using System.Threading;
using System.Threading.Tasks;

namespace CSharpCOnsole
{
    class Program
    {
        private static ManualResetEvent exit = new ManualResetEvent(false);

        static void Main(string[] args)
        {
            var t = Task.Factory.StartNew(F);
            Console.ReadKey();
            exit.Set();
            t.Wait();
            exit.Close();
        }

        static void F()
        {
            var dtStopwatch = new Stopwatch();
            uint ii = 0;
            while (!exit.WaitOne(25)) {
                dtStopwatch.Stop();
                var dt = 1000.0 * dtStopwatch.ElapsedTicks / Stopwatch.Frequency;
                dtStopwatch.Restart();

                if (ii++ % 40 == 0) {
                    Console.WriteLine(dt.ToString("F3"));
                }
            }
        }
    }
}

I know that it may sound stupid, but here is what happens: if I restart my PC, run VS right after boot and run this program, i get the following output:

31.665
31.365
31.541
...

What is more, if i change 25 in !exit.WaitOne(25) to any other number in range 16 to 31, i get the same result: it waits 31 ms. If i pick any number in range 1 to 15 it waits exactly 16 ms. And so on, if i pick any nuber in range 32 to 47 it waits exactly 48 ms. BUT: if I compile-and-run this code several times (about 10-30) or wait for some time (about 5-20 min after boot), it suddenly starts working fine! Yes, it sounds ridiculus, but it is what happens. It starts blocking the loop for the exact given time with 1 ms precision. And it keeps going until the next PC restart. I have tried this on two different PCs and got the same behaviour. Googling gave me absolutely nothing on this problem. If i run compiled EXE without VS, i get the same behaviour.

iamnp
  • 510
  • 8
  • 23
  • Sounds like it may just be some background service which starts up and modifies the precision of the clock at some point. – Jon Skeet Nov 05 '15 at 11:53
  • "it suddenly starts working fine" Probably, some other program raised the timer resolution. Chrome did that for a while. Drains the battery. – usr Nov 05 '15 at 12:08
  • Could be that the time slicing of processor time is put to a 16ms interval to switch between program execution while it is under too much load of the startup. Once it has more resources it goes back to be able to switch to the program/thread that wants the process time. You could check process explorer (sysinternals) to see load on system or task manager to get information on load. (I would assume that compiling is not the point but rather the time it needs to compile). Once the load is down check if you get the correct result. – Uwe Hafner Nov 05 '15 at 12:15

2 Answers2

1

Waiting on any sort of WaitHandle is guaranteed to wait at least as long as the timeout specified, but possibly / likely longer. The actual timeout is determined by the system clock, and by how busy the system is. If you want to do precision timing WaitHandles or classes using them (such as ManualResetEvent) are not the way to go.

lsedlacek
  • 313
  • 2
  • 6
  • So what do suggest to block game loop? Thread.Sleep has the same strange behaviour for 5-20 mins after PC startup – iamnp Nov 05 '15 at 11:54
  • Could just do `Thread.Sleep(0)` or `Thread.Yield()` in a cycle as suggested here: http://stackoverflow.com/a/25497775/5401421 – lsedlacek Nov 05 '15 at 16:10
-1

I have found the answer - you can set system timer's resolution by calling TimeBeginPeriod https://stackoverflow.com/a/15071477/1389883

Or you can use Multimedia Timer API: https://stackoverflow.com/a/24843946/1389883

Community
  • 1
  • 1
iamnp
  • 510
  • 8
  • 23
  • This sets the timer resolution globally. Please don't do this unless you know what you are doing, and are running on a dedicated computer. Consider [this](https://msdn.microsoft.com/en-us/library/windows/desktop/dd742877(v=vs.85).aspx) instead if you really need that kind of precision. – lsedlacek Nov 06 '15 at 12:03