简体   繁体   English

C#Timer对象如何确定何时经过了一段时间?

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

How does a C# Timer Object decide when an elapsed amount of time has occurred? C#Timer对象如何确定何时经过了一段时间?

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. 此外,最好知道在哪里找到C#源代码来浏览。

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. CLR创建一个专用线程,处理您在应用程序中创建的所有计时器对象,并运行其Elapsed事件和回调处理程序。 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. Project + Properties,Debug选项卡,勾选“启用本机代码调试”(又名非托管代码)选项。 Tools + Options, Debugging, Symbols and ensure that the Microsoft Symbol Server is enabled. 工具+选项,调试,符号并确保启用Microsoft Symbol Server。 Start debugging by pressing F11. 按F11开始调试。

Now use Debug + Windows + Threads. 现在使用Debug + Windows + Threads。 You'll see 4 threads listed. 您将看到列出的4个主题。 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. 继续步进,直到你跳过t.Start()方法调用。 Note that there's now a new thread added. 请注意,现在添加了一个线程。 The name is "ThreadPoolMgr::TimerThreadStart". 名称是“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. 这里的重要部分是TimerThreadStart()函数,它是CLR启动的线程的起点。 And the SleepEx() call, that's this thread sleeping until the next timer is due. 而SleepEx()调用,就是这个线程一直睡到下一个计时器到期。 This stack trace was for .NET 4.5, it has been consistent like this all the way back since .NET 2.0. 这个堆栈跟踪适用于.NET 4.5,它自.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 . 有关哪些源代码可用,您可以查看可在此处下载的SSCLI20源代码。 Searching that code takes you to the clr/src/vm/win32threadpool.cpp source code file. 搜索该代码将转到clr / src / vm / win32threadpool.cpp源代码文件。 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. 计时源是GetTickCount()api函数,与Environment.TickCount使用的函数相同。 The FireTimers() function sorts out which of the active timers is due first. FireTimers()函数可以先排出哪些活动计时器到期。 The SleepEx() function is the same one as Thread.Sleep(), except that it is alertable . SleepEx()函数与Thread.Sleep()函数相同,只是它是可警告的 It can be interrupted before the sleep is complete by an APC (asynchronous procedure call). 它可以在睡眠完成之前被APC(异步过程调用)中断。 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. 您看到在同一文件中使用的QueueUserAPC()函数,当线程需要终止时因为程序正在关闭以及添加或修改计时器以便需要计算新的睡眠。

The .NET BCL has at least two classes providing timers: .NET BCL至少有两个提供定时器的类:

However, the second class is implemented using the first. 但是,第二个类是使用第一个类实现的。 The first class uses some kind of native Win32 timer and Hans Passant has provided more information about the mechanism used. 第一类使用某种本机Win32计时器,Hans Passant提供了有关所用机制的更多信息。 In the BCL source code the timer is implemented using an internal class named TimerQueue . 在BCL源代码中,计时器是使用名为TimerQueue的内部类TimerQueue The implementation of the TimerQueue class has this comment: TimerQueue类的实现有这样的评论:

TimerQueue maintains a list of active timers in this AppDomain. TimerQueue维护此AppDomain中的活动计时器列表。 We use a single native timer, supplied by the VM, to schedule all managed timers in the AppDomain. 我们使用VM提供的单个本机计时器来安排AppDomain中的所有托管计时器。

So basically the AppDomain has a single time source implemented using unmanaged code. 所以基本上AppDomain有一个使用非托管代码实现的时间源。 See Hans Passant's answer for more information on that. 有关详细信息,请参阅Hans Passant的答案。


Also, it would be good to know where to find the C# Source Code to have a look through. 此外,最好知道在哪里找到C#源代码来浏览。

I use JetBrains dotPeek . 我使用JetBrains dotPeek It can be configured to retrieve source files from Microsoft if they exist. 它可以配置为从Microsoft检索源文件(如果存在)。 Simply load in the version of .NET you want to examine, hit Ctrl + T , type "Timer", select the specific class you want and it will download the source if it exists. 只需加载要检查的.NET版本, 按Ctrl + T ,键入“Timer”,选择所需的特定类,如果存在则下载源。 Otherwise you will see a decompiled version (depending on your configuration). 否则,您将看到反编译版本(取决于您的配置)。

Note that this tool will only allow you to decomple managed code and to fully understand the implementation of timers in .NET you need to be able to peek further into unmanaged code. 请注意,此工具仅允许您解析托管代码并完全理解.NET中的计时器实现,您需要能够进一步查看非托管代码。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM