C# 定时器改进版

目录

一、概述

二、常用定时器

三、实现功能

四、测试

结束


一、概述

前不久写了一篇名为 “C# 定时器封装版” 的帖子,它是用的定时器 + 事件订阅 的方式完成的,虽然可以实现需求,但是它有个缺点,就是定时器的执行的间隔时间只能用固定的时间,假设你想每个事件有自己的单独间隔时间那是不行,于是后面我在想如何解决这个问题,让加入的每一个委托都能设置自己的间隔时间,劈里啪啦乱写一通后,终于实现了,虽然写的不是特别好,但还是可以用的。

最近写了很多篇关于定时器帖子,包括一篇 C# 模拟 Unity3d 协程的帖子,也是为了解决大型项目中的定时器混乱问题,那么定时器使用过多会造成那些危害呢?

定时器会造成:

1.关闭程序之前,假设不关闭定时器,有时候程序都关闭不了,一直处于卡死的状态。

2.程序运行的时间长了,很容易闪退。

3.定时器没有统一管理,比较混乱,时间久了,自己都不记得用了几个定时器了。

如果用一个定时器,解决所有的需求,不更符合设计模式的“单一职责原则” 。

二、常用定时器

C# 自带的定时器有 线程定时器,Winform 定时器,WPF 同样也有,这里就展示其中的两种吧。

1.线程定时器

线程定时器也是在平时开发中用的比较多的一种定时器了,它和 Winform 最大的区别是,如果线程定时器调用 winform 相关的控件,会存在跨线程问题,在 DataTable 等表格刷新,也尽量不要使用线程定时器,可能会造成 DataTable 无法在界面显示。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

internal class TimerTest
{
    private System.Timers.Timer Timers1 = null;

    private void Init()
    {
        Timers1 = new System.Timers.Timer();
        Timers1.Interval = 3000;
        Timers1.AutoReset = true;
        Timers1.Elapsed += Timers1_Elapsed;
    }

    private void Timers1_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
    {
        Console.WriteLine("定时器");
    }

    public void Start()
    {
        Timers1.Enabled = true;
    }

    public void Stop()
    {
        Timers1.Enabled = false;
    }

    public TimerTest()
    {
        Init();
    }
}

2.控件定时器

在 winform、wpf 等平台的控件列表中,就自带了有控件定时器,它相对线程定时器的好处是,不用担心跨线程的问题,但在使用时也需要注意,不要在定时器中使用 while ,Thread.Sleep 等操作,这会导致界面卡死。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

internal class TimerTest
{
    private System.Windows.Forms.Timer Timers1 = null;

    private void Init()
    {
        Timers1 = new System.Windows.Forms.Timer();
        Timers1.Interval = 3000;
        Timers1.Tick += Timers1_Tick;
    }

    private void Timers1_Tick(object sender, EventArgs e)
    {
        Console.WriteLine("定时器");
    }

    public void Start()
    {
        Timers1.Enabled = true;
    }

    public void Stop()
    {
        Timers1.Enabled = false;
    }

    public TimerTest()
    {
        Init();
    }
}

三、实现功能

新建一个 winform 项目,给界面随便添加几个按钮,如下:

 添加一个类 TimerInfo

using System;

internal class TimerInfo
{
    /// <summary>
    /// 定时器的名字(关闭定时器用)
    /// </summary>
    public string TimerName { get; set; }
    /// <summary>
    /// 定时器委托
    /// </summary>
    public Action Tick { get; set; }
    /// <summary>
    /// 间隔时间(毫秒)
    /// </summary>
    public int Interval { get; set; }
    /// <summary>
    /// 开始执行的时间
    /// </summary>
    public DateTime StartTimer { get; set; }
}

TimerInfo 的作用相当于一个任务的结构体,任务的名字,委托,和间隔时间需要赋值,StartTimer 则是当前任务倒计时的开始时间,这里不用赋值。

添加一个类 TimeInterval

using System;

/// <summary>
/// 时间差计算
/// </summary>
public class TimeInterval
{
    /// <summary>
    /// 计算两个时间间隔的时长
    /// </summary>
    /// <param name="TimeType">返回的时间类型</param>
    /// <param name="StartTime">开始时间</param>
    /// <param name="EndTime">结束时间</param>
    /// <returns>返回间隔时间,间隔的时间类型根据参数 TimeType 区分</returns>
    public static double GetSpanTime(TimeType TimeType, DateTime StartTime, DateTime EndTime)
    {
        TimeSpan ts1 = new TimeSpan(StartTime.Ticks);
        TimeSpan ts2 = new TimeSpan(EndTime.Ticks);
        TimeSpan ts = ts1.Subtract(ts2).Duration();
        //TimeSpan ts = EndTime - StartTime;

        double result = 0f;
        switch (TimeType)
        {
            case TimeType.MilliSecond:
                result = ts.TotalMilliseconds;
                break;
            case TimeType.Seconds:
                result = ts.TotalSeconds;
                break;
            case TimeType.Minutes:
                result = ts.TotalMinutes;
                break;
            case TimeType.Hours:
                result = ts.TotalHours;
                break;
            case TimeType.Days:
                result = ts.TotalDays;
                break;
        }
        return result;
    }
    
    private TimeInterval() { }
}

/// <summary>
/// 时间类型
/// </summary>
public enum TimeType
{
    /// <summary>
    /// 毫秒
    /// </summary>
    MilliSecond,
    /// <summary>
    /// 秒
    /// </summary>
    Seconds,
    /// <summary>
    /// 分钟
    /// </summary>
    Minutes,
    /// <summary>
    /// 小时
    /// </summary>
    Hours,
    /// <summary>
    /// 天
    /// </summary>
    Days,
    /// <summary>
    /// 月
    /// </summary>
    Months
}

TimeInterval 类的主要作用是判断两个时间间隔了多久。

添加一个类 TimerOptimize

using System;
using System.Collections.Generic;
using System.Linq;

internal class TimerOptimize
{
    private static List<TimerInfo> TimerList = new List<TimerInfo>();

    private static List<string> RemoveList = new List<string>();

    private static System.Timers.Timer Timer1 = null;


    private static void Init()
    {
        Timer1 = new System.Timers.Timer();
        Timer1.Interval = 200;
        Timer1.AutoReset = true;
        Timer1.Elapsed += Timer1_Elapsed;
    }

    /// <summary>
    /// 添加定时器
    /// </summary>
    /// <param name="timer"></param>
    public static void Add(TimerInfo timer)
    {
        if (!Check(timer)) return;

        timer.StartTimer = DateTime.Now;
        TimerList.Add(timer);

        if (Timer1 != null && !Timer1.Enabled)
            Timer1.Enabled = true;
    }

    /// <summary>
    /// 移除定时器
    /// </summary>
    /// <param name="timerName"></param>
    public static void Remove(string timerName)
    {
        if (string.IsNullOrEmpty(timerName)) return;
        if (RemoveList.Contains(timerName)) return;
        var collect = TimerList.Where(p => p.TimerName == timerName).ToList();
        if (collect == null || collect.Count == 0) return;

        RemoveList.Add(timerName);
    }


    private static void Timer1_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
    {
        if (TimerList.Count > 0)
        {
            foreach (var item in TimerList)
            {
                double timer = TimeInterval.GetSpanTime(TimeType.MilliSecond, item.StartTimer, DateTime.Now);
                if (timer > item.Interval)
                {
                    item.StartTimer = DateTime.Now;
                    if (item.Tick != null)
                        item.Tick();
                }
            }
        }
        if (RemoveList.Count > 0)
        {
            List<int> removeIndexList = new List<int>();
            for (int i = 0; i < RemoveList.Count; i++)
            {
                int index = TimerList.FindIndex(p => p.TimerName == RemoveList[i]);
                if (index >= 0)
                {
                    TimerList.RemoveAt(index);
                    removeIndexList.Add(i);
                    Console.WriteLine("[TimerOptimize]移除定时器 {0}", RemoveList[i]);
                }
            }
            if (removeIndexList.Count > 0)
            {
                for (int i = 0; i < removeIndexList.Count; i++)
                {
                    RemoveList.RemoveAt(removeIndexList[i]);
                }
            }
            if (Timer1 != null && TimerList.Count == 0)
            {
                Timer1.Enabled = false;
                Console.WriteLine("[TimerOptimize]定时器已经关闭");
            }
        }
    }

    private static bool Check(TimerInfo timer)
    {
        if (timer == null)
        {
            Console.WriteLine("[TimerOptimize]timer 不能为空");
            return false;
        }
        if (timer.Tick == null)
        {
            Console.WriteLine("[TimerOptimize]timer.Tick 不能为空");
            return false;
        }
        if (string.IsNullOrEmpty(timer.TimerName))
        {
            Console.WriteLine("[TimerOptimize]timer.TimerName 不能为空");
            return false;
        }
        if(timer.Interval < 200)
        {
            Console.WriteLine("[TimerOptimize]timer.Interval 间隔时间太短");
            return false;
        }
        if (TimerList.Any(p => p.TimerName == timer.TimerName))
        {
            Console.WriteLine("[TimerOptimize]不能重复的添加,TimerName:{0}", timer.TimerName);
            return false;
        }
        return true;
    }

    static TimerOptimize()
    {
        Init();
    }

    private TimerOptimize()
    {
    }

    ~TimerOptimize()
    {
        Timer1.Enabled = false;
    }
}

TimerOptimize 类的主要作用是存储这些任务,判断什么时候执行回调,什么时候移除任务等。

主要代码都在这里了,下面开始测试。

四、测试

Form1 代码

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace 定时器
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        private void Form1_Load(object sender, EventArgs e)
        {
            
        }

        private void button1_Click(object sender, EventArgs e)
        {
            TimerOptimize.Add(new TimerInfo()
            {
                TimerName = "Timer1",
                Tick = Test1,
                Interval = 1000
            });
            Console.WriteLine("添加 timer1");
        }

        private void button2_Click(object sender, EventArgs e)
        {
            TimerOptimize.Add(new TimerInfo()
            {
                TimerName = "Timer2",
                Tick = Test2,
                Interval = 2000
            });
            Console.WriteLine("添加 timer2");
        }

        private void button3_Click(object sender, EventArgs e)
        {
            TimerOptimize.Add(new TimerInfo()
            {
                TimerName = "Timer3",
                Tick = Test3,
                Interval = 3000
            });
            Console.WriteLine("添加 timer3");
        }

        private void button4_Click(object sender, EventArgs e)
        {
            TimerOptimize.Remove("Timer1");
        }

        private void button5_Click(object sender, EventArgs e)
        {
            TimerOptimize.Remove("Timer2");
        }

        private void button6_Click(object sender, EventArgs e)
        {
            TimerOptimize.Remove("Timer3");
        }

        private void Test1()
        {
            Console.WriteLine("定时器1");
        }

        private void Test2()
        {
            Console.WriteLine("定时器2");
        }

        private void Test3()
        {
            Console.WriteLine("定时器3");
        }
    }
}

执行定时器1,并取消任务

执行定时器2,3,并取消任务

如果 TimerOptimize 类的 TimerList 为空时,会自动关闭定时器,这样能有效的节约系统资源,我粗略的看了下倒计时的时间,差不多是准确的,那么功能也就实现了,如果有需要改进的地方,欢迎留言告诉我,谢谢!

结束

如果这个帖子对你有所帮助,欢迎 关注 、点赞 、留言

end

猜你喜欢

转载自blog.csdn.net/qq_38693757/article/details/132025131