正确使用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上再次运行。运行事件。

我的问题是:

  1. 在给定规范的情况下,您会以不同的方式编写此行为吗?
  2. 在这种情况下是否可以使用单例,或者它没有意义?

正确使用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表达式,它定义了一个委托,该委托接受两个参数se(发送方和事件)。由于这被添加到_timer.Elapsed事件(ElapsedEventHandler类型)中,编译器可以从事件类型ElapsedEventHandler推断出se类型。我们的委托执行的唯一动作是doStuff()

可以展开为:

_timer.Elapsed += delegate(object sender, ElapsedEventArgs e){doStuff();};

lambda允许我们更简洁地编写上述代码。

我不知道你想达到什么目的,但这里有一些关于你当前设计的观点:

  1. 你的GetTimer在多线程模式下被打破:

    if (_timer == null) _timer = new UpdaterTimer();

    假设您有两个线程,每个线程同时调用GetTimer,第一个线程检查_timer并发现它为空,因此它继续。但是在到达_timer = new UpdateTimer()之前,线程上下文切换切换到另一个线程并暂停当前线程的执行。所以另一个线程检查_timer并发现它不是空的,所以它继续并创建一个新的计时器,现在上下文开关重新调度了第一个线程并继续执行,所以创建一个新的计时器并更新旧的。使用静态构造函数代替static UpdaterTimer() { _timer = new UpdaterTimer();}

  2. 开发者可以随心所欲地调用GetTimer(),因此它将再次调用_timer.Elapsed += new ElapsedEventHandler(_timer_Elapsed);,并注册另一个Elapsed处理程序。还要注意,无论何时调用GetTimer,计时器将启动"_timer.Start()",即使它已停止。

  3. 不返回底层定时器,而是公开一个公共方法Start(), Stop(), UpdateInterval(int interval)

  4. 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);