在 class 的所有实例中同步计时器的好方法是什么?

What would be a good way to sync the timers in all instances of a class?

也就是说,我有一个 class,其中包含一个静态 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
    }
}

因为计时器引用存储在 static 字段中,您一次只能有一个计时器实例可用。所以我不清楚你所说的 "sync the timers [plural]".

是什么意思

如果您希望 class 的每个实例在单个计时器的同一刻度上执行其 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
  • 我将计时器包裹在一个嵌套的静态 class 中,因为这样做允许我用一个事件公开计时器,这反过来又允许我忽略 thread-safe 修改的问题回调委托,因为编译器会自动为此生成必要的代码。
  • 您最初将 timer 作为值传递给构造函数的 state 参数。目前尚不清楚您打算这样做。下面是的事情:第一次初始化计时器时,该字段的值为null,所以state参数为null,这就是传递给处理程序的内容。对于 TestClass 的每个新实例,该实例将创建一个新计时器,但使用 先前创建的 计时器实例作为 state 值。在 TestClass 的每个实例中,当它的 DoStuff() 方法被调用时,它将接收对由 TestClass 对象的前一个实例创建的计时器的引用,或 null TestClass 对象的第一个实例创建。

    相反,我只是简单地初始化定时器而不用 state 值(传递 null),然后传递调用委托时计时器引用自身。这对我来说比你的代码所做的更有意义。
  • 静态事件始终必须非常小心地使用,因为它们本身始终存在,并且隐式保留对订阅该事件的任何对象的引用。通过以上内容,您的 TestClass 对象将 永远不会 成为 garbage-collected,因为没有任何机制可以从定时器事件中取消订阅对象。您可能需要考虑向取消订阅事件的 TestClass 对象添加一个方法,并且调用代码可以在它准备好丢弃给定的 TestClass 对象之前调用该方法。

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

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