【问题标题】:What would be a good way to sync the timers in all instances of a class?在类的所有实例中同步计时器的好方法是什么?
【发布时间】:2017-07-10 09:15:51
【问题描述】:

也就是说,我有一个包含静态 System.Threading.Timer 的类,我想同步所有对象中的计时器。下面是一个示例,我想要实现的是让所有对象同时调用DoStuff()

public class TestClass {
    public static Timer timer;
    public TestClass() {
        TimerCallback callback = DoStuff;
        timer = new Timer(callback, timer, 0, 500);
    }

    public void DoStuff(object source) {
    // Do stuff
    }
}

【问题讨论】:

  • 那不是有效的代码。无论如何,请阅读“多播委托”,与System.Timers.Timer 一起解决您的问题。
  • 谢谢。什么是无效的呢?它应该只是一些重命名,并从有效的实际代码中删除行?
  • 看起来您正试图在构造函数中声明成员数据——如果它应该是成员,则将其放入类中,或者删除 publicstatic 关键字(如果它是有意的)成为一个局部变量。
  • 啊,是的。固定。

标签: c# timer singleton static-classes


【解决方案1】:

由于计时器引用存储在static 字段中,因此您一次只能有一个计时器实例可用。所以我不清楚你所说的“同步计时器[复数]”是什么意思

如果您希望类的每个实例在单个计时器的同一滴答声上执行其 DoStuff() 方法,在我看来,正确的方法是维护一个静态处理程序,它调用每个实例更新的委托实例。例如:

public class TestClass {

    private static class TimerHandler
    {
        public static event TimerCallback TimerHandlers;

        private static readonly Timer timer = new Timer(TimerHandlerCallback, null, 0, 500);

        private static void TimerHandlerCallback(object state)
        {
            TimerHandlers?.Invoke(timer);
        }
    }

    public TestClass() {
        TimerHandler.TimerHandlers += DoStuff;
    }

    public void DoStuff(object source) {
    // Do stuff
    }
}

注意事项:

  • 无论你最终做什么,我认为没有理由将Timer 字段设为public
  • 我将计时器包装在一个嵌套的静态类中,因为这样做可以让我用事件公开计时器,这反过来又让我可以忽略回调委托的线程安全修改问题,因为编译器会自动将为此生成必要的代码。
  • 您最初将timer 作为值传递给构造函数的state 参数。目前尚不清楚您打算这样做。以下是发生的情况:第一次初始化计时器时,该字段的值为null,因此state 参数为null,这就是传递给处理程序的内容。对于TestClass 的每个新实例,该实例将创建一个新计时器,但使用以前创建的 计时器实例作为state 值。在TestClass 的每个实例中,当它的DoStuff() 方法被调用时,它将接收到由TestClass 对象的前一个实例创建的计时器的引用,或者null 的第一个TestClass 实例的引用对象已创建。

    相反,我只是在没有 state 值的情况下初始化定时器(传递 null),然后在调用委托时传递定时器引用本身。这对我来说比你的代码所做的更有意义。
  • 必须非常小心地使用静态事件,因为它们本身总是存在的,并且它们隐含地保留对订阅该事件的任何对象的引用。有了上述情况,您的 TestClass 对象将永远被垃圾收集,因为没有机制可以从计时器事件中取消订阅对象。您可能需要考虑向您的 TestClass 对象添加一个取消订阅事件的方法,并且调用代码可以在准备丢弃给定的 TestClass 对象之前调用该方法。

    这是否真的必须的,我无从得知。你的问题没有足够的上下文。如果这些对象永远不需要被 GC,那么你可以跳过这个。如果您确实需要这样做,您可能需要考虑实现IDisposable 作为调用方法的便捷机制(即让您的Dispose() 方法调用它),所以a)您可以使用using 语句来处理对象的生命周期,以及 b) 接口实现的存在提醒您需要手动管理对象的生命周期。在这里实现IDisposable 的一个警告(除了所有通常的其他警告)是您将无法依赖终结器作为错误代码的备份,因为这仅在对象实际上有资格成为 GC 时才有效'ed,在这种情况下,直到您调用 Dispose()

    此问题的另一种替代方法是使用弱引用实现 TimerHandlers 事件。如果这是在 WPF 程序的上下文中,您可以使用 WeakEventManager 类来简化此操作。如果不是,您可能会发现学习如何正确使用弱引用是不值得的,尤其是在事件的上下文中。再一次,如果没有更多的上下文,我很难说。无论哪种方式,使用弱引用的好处是事件代码保留的引用本身不会阻止对象被 GC'ed。您将一个实施细节挑战替换为另一个;好处是新的挑战更加自动化......一旦你解决了它,你就完成了,而不是每次创建 TestClass 对象的实例时都必须记住,以后你必须自己清理。缺点当然是,从概念上讲,弱引用可能比在丢弃对象之前必须清理对象的基本概念更难理解。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2010-10-26
    • 2016-03-27
    • 2012-12-13
    相关资源
    最近更新 更多