通过C#的System.Timers.Timer封装一个定时任务工具

Posted plumx

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了通过C#的System.Timers.Timer封装一个定时任务工具相关的知识,希望对你有一定的参考价值。

需求:

项目中需要用到一个独立于主项目之外的定时任务程序,让开发人员能够在该项目中编写自己的定时任务接口并按自己指定的时间间隔定时执行,通常用来定时查询数据库或者定时将一些内存数据保存为用于分析的文件。我负责写核心的定时任务模块,这只是临时用了个把小时做的一个简单的小模块,因为是第一次写博客,就用这个小功能作为开始吧。

实现前的准备与调研:

程序通过C#实现,那首先要考虑强大的.NET是否有方便的定时器,答案是肯定的,而且还不止一种:

1.System.Windows.Forms.Timer

2.System.Threading.Timer

3.System.Timers.Timer

从其他博客了解到主要有这三种定时器类,因为是临时紧急需求,没有来得及自己调研这三个定时器适合的场景和各自的优劣,不过网上已经有很多同行使用过,也作出了很多分析,我主要参考了如下的博客:

https://cloud.tencent.com/developer/article/1530659

最终决定使用System.Timers.Timer,以为他不依赖项目类型且相对精准;可以定时执行回调函数,使用简单;可设置参数多,比较灵活。

不过System.Timers.Timer有个缺点,每个定时任务被执行时实际上是在一个独立的逻辑线程中执行,无法让所有任务阻塞执行,也就是说如果一个任务非常耗时且大于了执行间隔时会同时有多个任务在执行:

 需求中的任务都需要顺序阻塞执行,所以需要对Timer做一些封装,让定时任务可以阻塞执行。设计的模块最好对外部使用者隐藏System.Timers.Timer的技术细节,只需要调用简单的调用Add和Remove接口就可以添加和移除定时任务,而且保证线程安全。

 

决定使用System.Timers.Timer后,还应该去MSDN上阅读一下技术文档:

https://docs.microsoft.com/en-us/dotnet/api/system.timers.timer?view=net-5.0

实现:

TimerObj类:

封装System.Timers.Timer,能够定时阻塞调用回调方法,并且需要捕获回调方法的异常并打印,可以设置回调方法的调用次数。

内部的timer实际上回调的是TimerCommonExcute函数,该函数再调用事件timerCallBack,在回调事件函数之前会阻塞定时器,并且根据调用次数callCount循环调用事件函数,当事件函数发生异常时捕获异常并打印,然后结束本次定时回调并重启定时器。

private class TimerObj
{
    //回调事件
    public event Action timerCallBack;
    //事件回调的次数
    public int callCount;
    //定时器的key
    public String timerKey;

    Timer t;

    TimerObj()
    { }

    //需要一个事件间隔与key值来初始化一个TimerObj
    public TimerObj(double timeInterval, string timerKey)
    {
        t = null;
        if (timeInterval > 0)
        {
            t = new Timer(timeInterval);
            t.Elapsed += TimerCommonExcute;
            t.AutoReset = true;
            callCount = 1;
            this.timerKey = timerKey;
        }
    }

    public bool IsTimerValid()
    {
        return t != null;
    }

    //开始执行Timer
    public void RunTimer()
    {
        if (IsTimerValid())
        {
            t.Start();
        }
    }

    //销毁Timer
    public void Destory()
    {
        if (IsTimerValid())
        {
            t.Stop();
            t.Dispose();
        }
        t = null;
    }

    //timer的实际回调函数
    void TimerCommonExcute(object source, ElapsedEventArgs e)
    {
        if (IsTimerValid())
        {
            if (timerCallBack == null)
                return;

            //阻塞定时器
            t.Stop();

            //保证异常安全
            for (int i = 0; i < callCount; i++)
            {
                try
                {
                    timerCallBack();
                    Console.WriteLine("Timer" + timerKey + " called " + (i + 1) + "!");
                }
                catch (Exception exp)
                {
                    Console.WriteLine("Timer" + timerKey + " exception!\\n Exception stack trace: " + exp.StackTrace);
                    break;
                }
            }
            //重启定时器
            t.Start();
        }
    }
}

 

TimerTask类:

设计为单例,对外提供:

bool AddTimerTask(Action callback, uint millisec)接口用于添加定时任务

void RemoveTimerTask(Action callback, uint millisec)接口用于移除定时任务

给被人使用的模块接口尽量简单,内部的技术细节也尽量隐藏。定时任务的函数形式暂时为无参无返回值:void func()。TimerObj作为TimerTask的私有类对外部透明。

内部通过一个字典管理所有的TimerObj,字典使用定时方法全类名+调用间隔组合成的字符串作为key,同一个key的定时任务被Add时实际会增加该TImerObj的调用次数,而不会再创建一个新的TimerObj。

public class TimerTask
{
    private class TimerObj
    {
        //回调事件
        public event Action timerCallBack;
        //事件回调的次数
        public int callCount;
        //定时器的key
        public String timerKey;

        Timer t;

        TimerObj()
        { }

        //需要一个事件间隔与key值来初始化一个TimerObj
        public TimerObj(double timeInterval, string timerKey)
        {
            t = null;
            if (timeInterval > 0)
            {
                t = new Timer(timeInterval);
                t.Elapsed += TimerCommonExcute;
                t.AutoReset = true;
                callCount = 1;
                this.timerKey = timerKey;
            }
        }

        public bool IsTimerValid()
        {
            return t != null;
        }

        //开始执行Timer
        public void RunTimer()
        {
            if (IsTimerValid())
            {
                t.Start();
            }
        }

        //销毁Timer
        public void Destory()
        {
            if (IsTimerValid())
            {
                t.Stop();
                t.Dispose();
            }
            t = null;
        }

        //timer的实际回调函数
        void TimerCommonExcute(object source, ElapsedEventArgs e)
        {
            if (IsTimerValid())
            {
                if (timerCallBack == null)
                    return;

                //阻塞定时器
                t.Stop();

                //保证异常安全
                for (int i = 0; i < callCount; i++)
                {
                    try
                    {
                        timerCallBack();
                        Console.WriteLine("Timer" + timerKey + " called " + (i + 1) + "!");
                    }
                    catch (Exception exp)
                    {
                        Console.WriteLine("Timer" + timerKey + " exception!\\n Exception stack trace: " + exp.StackTrace);
                        break;
                    }
                }
                //重启定时器
                t.Start();
            }
        }
    }

    //不完全懒汉单例
    public static TimerTask instance { get; private set; }
    static TimerTask()
    {
        instance = new TimerTask();
    }

    //字典锁,保证线程安全
    readonly object dicLock;
    Dictionary<string, TimerObj> timerDic;
    TimerTask()
    {
        timerDic = new Dictionary<string, TimerObj>();
        dicLock = new object();
    }

    //添加定时任务
    public bool AddTimerTask(Action callback, uint millisec)
    {
        if (callback == null || millisec == 0)
            return false;

        lock (dicLock)
        {
            if (timerDic != null)
            {
                bool isCreateTimer = true;
                string timerName = callback.Method.ReflectedType.FullName + "." + callback.Method.Name + "-" + millisec;
                //定时器已存在
                if (timerDic.ContainsKey(timerName))
                {
                    TimerObj tarTimer = timerDic[timerName];
                    if (tarTimer != null && tarTimer.IsTimerValid())
                    {
                        tarTimer.callCount++;
                        isCreateTimer = false;
                    }
                }

                if (isCreateTimer)
                {
                    timerDic.Remove(timerName);
                    TimerObj newTimer = new TimerObj(millisec, timerName);
                    newTimer.timerCallBack += callback;
                    newTimer.callCount = 1;
                    timerDic.Add(timerName, newTimer);
                    newTimer.RunTimer();
                }
            }
        }

        return false;
    }

    //移除定时任务
    public void RemoveTimerTask(Action callback, uint millisec)
    {
        if (callback == null || millisec == 0)
            return;

        lock (dicLock)
        {
            if (timerDic != null)
            {
                string timerName = callback.Method.ReflectedType.FullName + "." + callback.Method.Name + "-" + millisec;
                if (timerDic.ContainsKey(timerName))
                {
                    TimerObj timer = timerDic[timerName];
                    timer.callCount--;
                    if (timer.callCount <= 0)
                    {
                        timer.Destory();
                        timerDic.Remove(timerName);
                    }
                }
            }
        }
    }
}

测试:

添加两次间隔2秒执行的TimerTask1任务;添加一次间隔5s执行的TimerTask2任务,内部抛出异常;使用timer添加一个6秒定时只调用一次的任务OnceTimerTask3,该任务会Remove之前添加的TimerTask2.

using System;
using System.Timers;

namespace TimerTaskApp
{
    class Program
    {
        static void TimerTask1()
        {
            for (int i = 0; i < 10000; i++)
            {
                double a = Math.Sqrt(i);
                double b = Math.Pow(a, 2.7);            
            }
        }

        static void TimerTask2()
        {
            throw new Exception("Task2 exception");
        }

        static void OnceTimerTask3(object obj, ElapsedEventArgs eArgs)
        {
            MyTimerTask.TimerTask.instance.RemoveTimerTask(TimerTask2, 5000);
        }

        static void Main(string[] args)
        {
            MyTimerTask.TimerTask.instance.AddTimerTask(TimerTask1, 2000);
            MyTimerTask.TimerTask.instance.AddTimerTask(TimerTask1, 2000);
            MyTimerTask.TimerTask.instance.AddTimerTask(TimerTask2, 5000);

            System.Timers.Timer rTimer = new System.Timers.Timer(6000);
            rTimer.AutoReset = false;
            rTimer.Elapsed += OnceTimerTask3;
            rTimer.Enabled = true;

            Console.ReadLine();
            Console.WriteLine("Terminating the application...");
        }
    }
}

控制台打印结果:

 

 

 可以看到TimerTask1和TimerTask2都正确定时执行了,OnceTimerTask3在6s后移除了TimerTask2的任务,所以TimerTask2只调用了一次。

总结:

System.Timers.Timer实际上是可以通过属性AutoReset设置是只间隔调用一次还是反复调用的,如果需要增加一次调用的需求可以将该属性也封装到接口参数中。

 

以上是关于通过C#的System.Timers.Timer封装一个定时任务工具的主要内容,如果未能解决你的问题,请参考以下文章

[C#]System.Timers.Timer

使用System.Timers.Timer类实现程序定时执行

C# system.timers.timer 奇怪的行为

C# System.Timers.Timer 类已用事件和定时器的一般注意事项

c# 怎么设置 System.Timers.Timer执行次数 t5.AutoReset = false;

System.Timers.Timer 每秒最多只能提供 64 帧