正确使用Timer类
本文关键字:Timer | 更新日期: 2023-09-27 18:02:22
我想知道我写的代码是否写得很好,它确实起作用,但我以前做过糟糕的设计,所以我需要知道我是否以适当的方式思考这一点。
代码是关于使用System.Timers.Timer每X小时做一个重复的动作。我在stackoverflow上读了一些关于这个主题的帖子,然后我试着写自己的类。以下是我写的:
namespace MyTool
{
public class UpdaterTimer : Timer
{
private static UpdaterTimer _timer;
protected UpdaterTimer()
{ }
public static UpdaterTimer GetTimer()
{
if (_timer == null)
_timer = new UpdaterTimer();
SetTimer(_timer);
return _timer;
}
private static void SetTimer(UpdaterTimer _timer)
{
_timer.AutoReset = true;
_timer.Interval = Utils.TimeBetweenChecksInMiliseconds;
_timer.Elapsed += new ElapsedEventHandler(_timer_Elapsed);
_timer.Start();
DoStuff();
}
static void _timer_Elapsed(object sender, ElapsedEventArgs e)
{
DoStuff();
}
private static void DoStuff()
{
//does stuff on each elapsed event occurrence
}
}
}
简短描述:
- 尝试使用单例模式,因为我只需要一个计时器来工作
- 我不确定是否在SetTimer()方法中调用DoStuff(),这似乎是多余的。但逻辑是,当应用程序启动时,DoStuff()必须运行,然后它必须在每个Timer上再次运行。运行事件。
我的问题是:
- 在给定规范的情况下,您会以不同的方式编写此行为吗?
- 在这种情况下是否可以使用单例,或者它没有意义?
我冒昧地说,创建Timer的子类没有什么用处。你已经把大约7行代码变成了一大堆臃肿的东西。考虑到您的DoStuff
方法是私有的,这将不会以任何方式被重用。因此,考虑以下几行:
Action doStuff=()=>{
//does stuff on each elapsed event occurance
};
_timer=new System.Timers.Timer(){
AutoReset = true,
Interval = Utils.TimeBetweenChecksInMiliseconds
};
_timer.Elapsed += (s,e)=>doStuff();
_timer.Start();
doStuff();
,其中_timer
是包含类的一个属性。这里的意图很明显。我认为它不值得一个单独的类。
编辑:
(s,e)=>doStuff()
是一个lambda表达式,它定义了一个委托,该委托接受两个参数s
和e
(发送方和事件)。由于这被添加到_timer.Elapsed
事件(ElapsedEventHandler
类型)中,编译器可以从事件类型ElapsedEventHandler
推断出s
和e
类型。我们的委托执行的唯一动作是doStuff()
。
可以展开为:
_timer.Elapsed += delegate(object sender, ElapsedEventArgs e){doStuff();};
lambda允许我们更简洁地编写上述代码。
我不知道你想达到什么目的,但这里有一些关于你当前设计的观点:
-
你的
GetTimer
在多线程模式下被打破:if (_timer == null) _timer = new UpdaterTimer();
假设您有两个线程,每个线程同时调用
GetTimer
,第一个线程检查_timer并发现它为空,因此它继续。但是在到达_timer = new UpdateTimer()
之前,线程上下文切换切换到另一个线程并暂停当前线程的执行。所以另一个线程检查_timer
并发现它不是空的,所以它继续并创建一个新的计时器,现在上下文开关重新调度了第一个线程并继续执行,所以创建一个新的计时器并更新旧的。使用静态构造函数代替static UpdaterTimer() { _timer = new UpdaterTimer();}
-
开发者可以随心所欲地调用
GetTimer()
,因此它将再次调用_timer.Elapsed += new ElapsedEventHandler(_timer_Elapsed);
,并注册另一个Elapsed
处理程序。还要注意,无论何时调用GetTimer
,计时器将启动"_timer.Start()
",即使它已停止。 -
不返回底层定时器,而是公开一个公共方法
Start()
,Stop()
,UpdateInterval(int interval)
。 -
在
SetTimer()
你想立即调用DoStuff
,但那将被阻塞在SetTimer
方法等待DoStuff()
完成,一个更好的方法是启动该方法在一个新的线程ThreadPool.QueueUserWorkItem(new WaitCallback((_) => DoStuff()));
或使用System.Threading.Timer代替System.Timers.Timer
,并设置它调用方法立即开始。
我认为你应该在这里使用组合而不是继承。(即不继承定时器,只是使用定时器作为一个私有字段)
此外,如果你的类之外的代码不应该改变定时器的属性,你不应该显示它给其他类。如果你想让外部代码订阅定时器的Elapsed
事件,你可以为它添加一个事件,并在你的DoStuff
处理程序中触发它,如果不是——就隐藏它。
代码示例,OP在注释中要求:
注意:它不匹配规范(这是一个完全不同的用例,对于你的用例来说,你根本不需要一个单独的类(参考另一个答案)),但它展示了如何使用组合和隐藏实现细节。(这个例子也有一些线程安全问题,但这不是重点。)
public static class TimerHelper
{
private static Timer _timer;
static TimerHelper()
{
_timer = new Timer(){AutoReset=true, Interval=1000};
_timer.Start();
}
public static event ElapsedEventHandler Elapsed
{
add { _timer.Elapsed += value; }
remove { _timer.Elapsed -= value; }
}
}
使用:
// somewhere else in code
TimerHelper.Elapsed += new ElapsedEventHandler(TimerHelper_Elapsed);