Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How does a C# Timer Object decide when an elapsed amount of time has occurred?

Tags:

c#

timer

How does a C# Timer Object decide when an elapsed amount of time has occurred?

I am wondering if it simply loops or is more intelligent than this.

Also, it would be good to know where to find the C# Source Code to have a look through.

like image 296
pengibot Avatar asked Dec 21 '22 14:12

pengibot


1 Answers

The CLR creates a dedicated thread that handles all timer objects you create in your application and gets their Elapsed event and callback handler running. You can see it being used with the debugger. Start with a console mode app that looks like this:

using System;
using System.Timers;

class Program {
    static void Main(string[] args) {
        var t = Timer();
        t.Elapsed += ElapsedEventHandler((s, e) => { });
        t.Start();
    }
}

Project + Properties, Debug tab, tick the "Enable native code debugging" (aka unmanaged code) option. Tools + Options, Debugging, Symbols and ensure that the Microsoft Symbol Server is enabled. Start debugging by pressing F11.

Now use Debug + Windows + Threads. You'll see 4 threads listed. Your main thread, the finalizer thread, the debugging thread and an idle threadpool thread. Keep stepping until you stepped past the t.Start() method call. Note that there's now a new thread added. The name is "ThreadPoolMgr::TimerThreadStart". Double-click it and look at the Call Stack window. You'll see:

ntdll.dll!_NtDelayExecution@8()  + 0x15 bytes   
ntdll.dll!_NtDelayExecution@8()  + 0x15 bytes   
KernelBase.dll!_SleepEx@8()  + 0x39 bytes   
clr.dll!ThreadpoolMgr::TimerThreadFire()  + 0x3e bytes  
clr.dll!ThreadpoolMgr::TimerThreadStart()  + 0x6a bytes 
kernel32.dll!@BaseThreadInitThunk@12()  + 0x12 bytes    
ntdll.dll!___RtlUserThreadStart@8()  + 0x27 bytes   
ntdll.dll!__RtlUserThreadStart@8()  + 0x1b bytes

The important bits here are the TimerThreadStart() function, that's the start point for the thread that the CLR started. And the SleepEx() call, that's this thread sleeping until the next timer is due. This stack trace was for .NET 4.5, it has been consistent like this all the way back since .NET 2.0. For which source code is available, you can have a look see at the SSCLI20 source code that you can download here. Searching that code takes you to the clr/src/vm/win32threadpool.cpp source code file. Have a look at it to see what is going on.

I'll just briefly describe it. The timing source is the GetTickCount() api function, the same one used by Environment.TickCount. The FireTimers() function sorts out which of the active timers is due first. The SleepEx() function is the same one as Thread.Sleep(), except that it is alertable. It can be interrupted before the sleep is complete by an APC (asynchronous procedure call). Which you see being used in that same file, QueueUserAPC() function when the thread needs to terminate because the program is shutting down and when a timer is added or modified so that a new sleep needs to be calculated.

like image 197
Hans Passant Avatar answered May 18 '23 21:05

Hans Passant