对于定时任务,一般由时间戳Timers 或者死循环While(true) 操作,两者都能达到,指定间隔时间内执行去执行任务,这里不做效率的比较,只说明一下适合的场景。先拿while来说,执行完成任务后,设置好线程等待时间即可,它有一优势,即如果此次任务未执行完,则不会进入下一次,也就是说是可控的,不会同时执行两笔相同任务
public void StartLoadTask() { while (true) { //... 业务逻辑//等待60秒,进入下一次 Thread.Sleep(60000); } }
再来看一看Times ,这里仅作 System.Timers 下的timer的说明(非 Threading 中的timer),如下图
//执行定时服务 var timer = new Timer() { AutoReset = true, Enabled = true, Interval=60000 }; timer.Elapsed += (sender, eventArgs) => Helper.StartTask();
可以满足每隔60s 调用一次 StartTask 方法,但是会有一个问题,试想如果由于某个内部或外部原因,导致执行一次程序的时间超过了60s,如果直接这样写,则会出现第一个任务还未执行完,下一个任务又进来,则同时运行两笔任务的情况,显然是不可取的,需要进行改进,即需等上次任务执行完,再去等待间隔,然后进入下一次任务,否则永远要等上一笔任务执行完。这里将 timer 当做参数 传递给 StartTask 方法,则
public void StartTask(Timer timer) { timer.Stop(); //... 业务逻辑 timer.Start(); }
看似两种操作都达到了需求,细想下来,还有不妥。试想任务是每分钟执行一次的,虽然这样处理解决了不会同时执行多笔任务的问题,可若任务执行时间超过了60s,则错过了下一次任务的执行时间。这样的话岂不是顾此失彼,既要每次只执行一笔任务,又不能错过每一次任务。想要满足这种要求,则只能引用安全队列机制,每隔60s,便把任务写入队列中,再启一个线程去循环读取队列里的任务便可。这样一个线程尽管往里面写,另外一个线程负责取,每执行完一笔任务,就去读下一笔任务,如此往复。
写入队列如下
public void StartTask() { //任务t //写入任务队列 TaskQueue.Enqueue(t); LogHelper.LogQueue.Enqueue("写入报告任务:" + t.Date); }
取出队列如下
public void StartTask() { while (true) { if (!TaskQueue.IsEmpty) { //取出任务 TaskQueue.TryDequeue(out ICCTask task); LogHelper.LogQueue.Enqueue("取出报告任务:" + task.Date); //执行任务 逻辑 // ... } Thread.Sleep(60000); } }
当然取出队列任务 需要设置线程为 异步 Task.Factory.StartNew(StartTask);