我一直在阅读 Illustrated C# 2008由 Daniel Solis 撰写(一本很棒的书,顺便说一句),并决定花更多时间在 Events 上,以加深我对该主题的理解。我试图了解为什么我每次运行该程序时都会看到不同的结果,以及我可以从中学到什么。
(下面的源代码)在本书的示例代码中,有一个MyTimerClass
,它有一个订阅到System.Timers.Timer
的事件。还有另外两个类,ClassA 和 ClassB,它们都有写入控制台的事件处理程序(一个是静态的,另一个不是)。在程序的 main
函数中,这些类的事件处理程序与 MyTimerClass
实例中的事件相关联。另一个函数通过 lambda 表达式添加到事件成员。
在使用作者的代码后,我决定添加另一个类,ClassC
。我没有在程序的 Main
函数中添加事件处理程序,而是决定在 ClassC
的构造函数中创建一个单独的 MyTimerClass
对象,它然后订阅 MyTimerClass
的事件。
当我在 3 个不同的场合运行我的代码 4.25 秒时,我的结果总是以不同的顺序排列。 Main
函数中的事件始终以相同的顺序调用 ClassA
、ClassB
,然后是 Lambda。但是,ClassC
的另一个事件似乎总是以完全随机的顺序调用。我还注意到第一组方法调用的时间似乎略有不同,而后续组的时间都相同。这是为什么?
(1) Event 1 - ClassA - 51:259
Event 2 - ClassC - 51:259
(2) Event 1 - ClassB - 51:261
(3) Event 1 - Lambda - 51:262
(1) Event 1 - ClassA - 52:271
(2) Event 1 - ClassB - 52:271
(3) Event 1 - Lambda - 52:271
Event 2 - ClassC - 52:271
(1) Event 1 - ClassA - 53:285
(2) Event 1 - ClassB - 53:285
(3) Event 1 - Lambda - 53:285
Event 2 - ClassC - 53:285
(1) Event 1 - ClassA - 54:299
(2) Event 1 - ClassB - 54:299
(3) Event 1 - Lambda - 54:299
Event 2 - ClassC - 54:299
(1) Event 1 - ClassA - 17:30
Event 2 - ClassC - 17:30
(2) Event 1 - ClassB - 17:32
(3) Event 1 - Lambda - 17:33
(1) Event 1 - ClassA - 18:42
(2) Event 1 - ClassB - 18:42
(3) Event 1 - Lambda - 18:42
Event 2 - ClassC - 18:42
(1) Event 1 - ClassA - 19:56
(2) Event 1 - ClassB - 19:56
(3) Event 1 - Lambda - 19:56
Event 2 - ClassC - 19:56
Event 2 - ClassC - 20:70
(1) Event 1 - ClassA - 20:70
(2) Event 1 - ClassB - 20:70
(3) Event 1 - Lambda - 20:70
(1) Event 1 - ClassA - 45:220
Event 2 - ClassC - 45:221
(2) Event 1 - ClassB - 45:223
(3) Event 1 - Lambda - 45:223
(1) Event 1 - ClassA - 46:232
(2) Event 1 - ClassB - 46:232
(3) Event 1 - Lambda - 46:232
Event 2 - ClassC - 46:232
Event 2 - ClassC - 47:246
(1) Event 1 - ClassA - 47:246
(2) Event 1 - ClassB - 47:246
(3) Event 1 - Lambda - 47:246
(1) Event 1 - ClassA - 48:260
(2) Event 1 - ClassB - 48:260
(3) Event 1 - Lambda - 48:260
Event 2 - ClassC - 48:260
这是我的控制台应用程序的源代码:
class Program
{
static void Main(string[] args)
{
MyTimerClass mc = new MyTimerClass();
ClassA ca = new ClassA();
ClassC cc = new ClassC();
mc.MyElapsed += ca.TimerHandlerA;
mc.MyElapsed += ClassB.TimerHandlerB;
mc.MyElapsed += (obj, e) =>
{
Console.WriteLine("(3) Event 1 - Lambda - {0}:{1}",
System.DateTime.Now.Second,
System.DateTime.Now.Millisecond);
};
Thread.Sleep(4250);
}
}
class ClassA
{
public void TimerHandlerA(Object obj, EventArgs e)
{
Console.WriteLine("(1) Event 1 - ClassA - {0}:{1}",
System.DateTime.Now.Second,
System.DateTime.Now.Millisecond);
}
}
class ClassB
{
public static void TimerHandlerB(Object obj, EventArgs e)
{
Console.WriteLine("(2) Event 1 - ClassB - {0}:{1}",
System.DateTime.Now.Second,
System.DateTime.Now.Millisecond);
}
}
class ClassC
{
public void TimerHandlerC(Object obj, EventArgs e)
{
Console.WriteLine(" Event 2 - ClassC - {0}:{1}",
System.DateTime.Now.Second,
System.DateTime.Now.Millisecond);
}
public ClassC()
{
// This will create a separate MyTimerClass and
// attach ClassC's event handler to mc's event.
MyTimerClass mc = new MyTimerClass();
mc.MyElapsed += TimerHandlerC;
}
}
public class MyTimerClass
{
public event EventHandler MyElapsed;
private void OnOneSecond(Object obj, EventArgs e)
{
if (MyElapsed != null)
MyElapsed(obj, e);
}
private System.Timers.Timer MyPrivateTimer;
public MyTimerClass()
{
MyPrivateTimer = new System.Timers.Timer();
// This will attach the OnOneSecond Event Handler
// to the system timer which will then raise
// MyElapsed.
MyPrivateTimer.Elapsed += OnOneSecond;
// This sets the interval at 1 second.
MyPrivateTimer.Interval = 1000;
// This turns the timer on when the the class
// is instantiated.
MyPrivateTimer.Enabled = true;
}
}
三个问题:
- 为什么每次的结果都不一样,是什么原因造成的?
- 为什么第一 block 结果中的时间略有不同,而后续 block 的时间相同?
- 我应该从这个例子中学到什么?
最佳答案
简短的回答是,“这就是 Windows 定时器的工作方式。”您在这里没有做错任何事情,您看到的结果是正常行为。
Microsoft 在 Windows 中提供的 native 计时器不能保证其计时准确。如果您将计时器设置为每 1000 毫秒计时一次,则可以保证它是至少 1000 毫秒,但不是恰好 1000 毫秒。它通常会非常接近,有时甚至是精确的,但如果您需要非常高精度的计时,则需要研究其他机制。在 Win32 C/C++ API 级别,适当的机制是 QueryPerformanceFrequency/QueryPerformanceCounter 方法。在 .NET 中,您将使用 StopWatch类来获得这个功能。
至于顺序,如果您在 .NET 中将多个处理程序附加到一个计时器,我很确定也不能保证它们会按任何给定的顺序执行。我不确定微软在幕后使用什么算法来触发它们,但你的结果清楚地表明它不是保证它们有序的算法。如果您需要按顺序排列代码,请为计时器注册一个 处理程序,并以正确的顺序调用该处理程序中的所有内容。
如评论中所述,Windows 并非真正设计为高性能实时操作系统。然而,这对于 99.99% 的桌面应用程序来说并不是真正必要的(因此 Microsoft 的设计决策;毕竟,它主要是一个桌面操作系统)。
关于c# - 为什么这些 Timer 事件会在不一致的时间引发?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/1705433/