CLR创建一个专用线程,处理您在应用程序中创建的所有计时器对象,并运行其Elapsed事件和回调处理程序.您可以看到它与调试器一起使用.从控制台模式应用程序开始,如下所示:
using System;
using System.Timers;
class Program {
static void Main(string[] args) {
var t = Timer();
t.Elapsed += ElapsedEventHandler((s, e) => { });
t.Start();
}
}
Run Code Online (Sandbox Code Playgroud)
Project + Properties,Debug选项卡,勾选"启用本机代码调试"(又名非托管代码)选项.工具+选项,调试,符号并确保启用Microsoft Symbol Server.按F11开始调试.
现在使用Debug + Windows + Threads.您将看到列出的4个主题.您的主线程,终结器线程,调试线程和空闲线程池线程.继续步进,直到你跳过t.Start()方法调用.请注意,现在添加了一个新线程.名称是"ThreadPoolMgr :: TimerThreadStart".双击它并查看"调用堆栈"窗口.你会看到的:
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
Run Code Online (Sandbox Code Playgroud)
这里的重要位是TimerThreadStart()函数,它是CLR启动的线程的起点.而SleepEx()调用,就是这个线程一直睡到下一个计时器到期.这个堆栈跟踪适用于.NET 4.5,它自.NET 2.0以来一直像这样一致.有关哪些源代码可用,您可以查看可在此处下载的SSCLI20源代码.搜索该代码将转到clr/src/vm/win32threadpool.cpp源代码文件.看看它看看发生了什么.
我只是简单地描述一下.计时源是GetTickCount()api函数,与Environment.TickCount使用的函数相同.FireTimers()函数可以先排出哪些活动计时器到期.SleepEx()函数与Thread.Sleep()函数相同,只是它是可警告的.它可以在睡眠完成之前被APC(异步过程调用)中断.您看到在同一文件中使用的QueueUserAPC()函数,当线程需要终止时因为程序正在关闭以及添加或修改计时器以便需要计算新的睡眠时.
.NET BCL 至少有两个提供计时器的类:
然而,第二个类是使用第一个类实现的。第一类使用某种本机 Win32 计时器,Hans Passant 提供了有关所用机制的更多信息。在 BCL 源代码中,计时器是使用名为 的内部类实现的TimerQueue。该类的实现TimerQueue有这样的注释:
TimerQueue 在此 AppDomain 中维护活动计时器的列表。我们使用 VM 提供的单个本机计时器来调度 AppDomain 中的所有托管计时器。
因此基本上 AppDomain 有一个使用非托管代码实现的单一时间源。有关更多信息,请参阅 Hans Passant 的回答。
另外,最好知道在哪里可以找到 C# 源代码来查看。
我使用JetBrains dotPeek。可以将其配置为从 Microsoft 检索源文件(如果存在)。只需加载您想要检查的 .NET 版本,点击Ctrl+ T,输入“Timer”,选择您想要的特定类,它就会下载源(如果存在)。否则您将看到反编译版本(取决于您的配置)。
请注意,此工具仅允许您反编译托管代码,并且要完全理解 .NET 中计时器的实现,您需要能够进一步了解非托管代码。
| 归档时间: |
|
| 查看次数: |
454 次 |
| 最近记录: |