【问题标题】:Why are these Timer events raised at inconsistent times?为什么这些 Timer 事件在不一致的时间引发?
【发布时间】:2009-12-11 04:09:35
【问题描述】:

我一直在阅读 Daniel Solis 的 Illustrated C# 2008(一本好书,顺便说一句),并决定多花一点时间在事件上,以加深我对这个主题的理解。我试图了解为什么每次运行程序都会看到不同的结果,以及我可以从中学到什么。

(下面的源代码)在本书的示例代码中,有一个MyTimerClass 有一个订阅System.Timers.Timer 的事件。还有另外两个类,ClassA 和 ClassB,它们都具有写入控制台的事件处理程序(一个是静态的,而另一个不是)。在程序的main 函数中,这些类的事件处理程序与MyTimerClass 实例中的事件相关联。另一个函数通过 lambda 表达式添加到事件成员。

使用作者的代码后,我决定添加另一个类,ClassC。我没有在程序的Main 函数中添加事件处理程序,而是决定在ClassC 的构造函数中创建一个单独的MyTimerClass 对象,然后订阅MyTimerClass 的事件。

当我在 3 个不同的场合运行我的代码 4.25 秒时,我的结果总是以不同的顺序排列。订单的Main 函数中的事件始终以相同的顺序调用ClassAClassB,然后是 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;
    }
}

三个问题:

  • 为什么每次结果都不一样?是什么原因造成的?
  • 为什么第一个结果块中的时间略有偏差,而后续块的时间相同?
  • 我应该从这个例子中学到什么?

【问题讨论】:

    标签: c# .net


    【解决方案1】:

    简短的回答是,“这就是 Windows 计时器的工作方式。”您在这里没有做错任何事情,您看到的结果是正常行为。

    Microsoft 在 Windows 中提供的本机计时器不保证其计时准确。如果您将计时器设置为每 1000 毫秒计时一次,则可以保证它至少 1000 毫秒,但不会完全 1000 毫秒。它通常会非常接近,有时甚至是精确的,但如果您需要非常高精度的计时,则需要研究其他机制。在 Win32 C/C++ API 级别,适当的机制是 QueryPerformanceFrequency/QueryPerformanceCounter 方法。在 .NET 中,您可以使用 StopWatch 类来获得此功能。

    至于顺序,如果您将多个处理程序附加到 .NET 中的一个计时器,我很确定也不能保证它们将以任何给定的顺序执行。我不确定微软到底在使用什么算法来解雇它们,但你的结果清楚地表明它不是保证它们井然有序的算法。如果您需要按顺序排列代码,请为计时器注册 一个 处理程序,并以正确的顺序从该处理程序调用所有内容。

    正如 cmets 中所述,Windows 并非真正设计为高性能实时操作系统。但是,对于 99.99% 的桌面应用程序来说,这并不是真正必要的(因此微软的设计决定;毕竟,它主要是桌面操作系统)。

    【讨论】:

    • QPC 方法通过 StopWatch 类在 .NET 中公开。但归根结底,Windows 并不是为用户应用程序而设计的“实时”操作系统;精确的亚毫秒计时很难做到。
    • 感谢秒表说明,以及关于 Windows 的精彩观点。我已经相应地更新了我的答案。
    • 感谢两位的解释。我并不关心时间,而是关心导致事件以不同顺序触发的原因。我从 Russell 的解释中得到的最好的东西是注册一个事件处理程序,这样我的代码就可以保证按照我期望的顺序执行。
    猜你喜欢
    • 1970-01-01
    • 2017-01-31
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-03-30
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多