在 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
对象的实例时都必须记住,以后你必须自己清理它。缺点当然是从概念上讲,弱引用可能比在丢弃对象之前必须清理对象的基本思想更难理解。
也就是说,我有一个 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
对象的实例时都必须记住,以后你必须自己清理它。缺点当然是从概念上讲,弱引用可能比在丢弃对象之前必须清理对象的基本思想更难理解。