C# Timer Improved Version

Table of contents

I. Overview

2. Commonly used timers

Three, realize the function

4. Test

Finish


I. Overview

Not long ago, I wrote a post called "C# Timer Encapsulation Version". It is completed by the method of timer + event subscription. Although the requirements can be realized, it has a shortcoming, which is the interval between the execution of the timer. The time can only use a fixed time, assuming that you want each event to have its own separate interval time, that’s not okay, so I’m thinking about how to solve this problem later, so that each commission that joins can set its own interval time. After writing a pass, it finally came true. Although the writing is not particularly good, it can still be used.

Recently, I have written a lot of posts about timers, including a post about C# simulating Unity3d coroutines, which is also to solve the problem of timer confusion in large projects. So what harm will be caused by using too many timers?

The timer will cause:

1. Before closing the program, if the timer is not closed, sometimes the program cannot be closed, and it is always stuck.

2. The program runs for a long time, and it is easy to crash.

3. There is no unified management of the timers, which is quite confusing. After a long time, I don't remember how many timers I used.

If a timer is used to solve all the requirements, it is not more in line with the "single responsibility principle" of the design pattern.

2. Commonly used timers

The timers that come with C# include thread timers, Winform timers, and WPF also have them. Here are two of them.

1. Thread timer

Thread timer is also a kind of timer that is often used in normal development. The biggest difference between it and Winform is that if the thread timer calls winform-related controls, there will be cross-thread problems. Table refreshes such as DataTable will also Try not to use the thread timer, which may cause the DataTable not to be displayed on the interface.

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. Control timer

In the control list of platforms such as winform and wpf, there is a control timer. Its advantage over thread timers is that you don't have to worry about cross-thread problems, but you also need to pay attention when using it, and don't use it in timers. While , Thread.Sleep and other operations, this will cause the interface to freeze.

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();
    }
}

Three, realize the function

Create a new winform project and add a few buttons to the interface, as follows:

 Add a class 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; }
}

The role of TimerInfo is equivalent to the structure of a task. The name, commission, and interval of the task need to be assigned. StartTimer is the start time of the countdown of the current task. There is no need to assign a value here.

Add a class 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
}

The main function of the TimeInterval class is to determine how long the interval between two times is.

Add a class 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;
    }
}

The main function of the TimerOptimize class is to store these tasks, determine when to execute callbacks, and when to remove tasks.

The main code is here, let's start testing.

4. Test

Form1 code

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");
        }
    }
}

Execute timer 1 and cancel the task

Execute timer 2, 3, and cancel the task

If the TimerList of the TimerOptimize class is empty, the timer will be automatically turned off, which can effectively save system resources. I roughly checked the countdown time, and it is almost accurate, so the function is realized. If there is any need to improve place, welcome to leave a message to tell me, thank you!

Finish

If this post is helpful to you, please pay attention, like and leave a message

end

Guess you like

Origin blog.csdn.net/qq_38693757/article/details/132025131