首页 > 技术文章 > C#如何重启一个计时器

Jeffrey-Chou 2020-02-26 11:39 原文

一. 废话

今天在做项目的时候遇到了如何重启一个计时器的问题,C# 中有很多计时器,但是它们还真的没有一个用来  " Restart " 的方法。

二. 没用的分类

C# 系统中有好多种类的计时器:

  • System.Timers.Timer
  • System.Threading.Timer
  • System. Windows.Threading.DispatcherTimer
  • System.Windows.Forms.Timer

三. 强行增加篇幅贴代码

这边先使用 System.Timers.Timer 来做一下测试的代码演示。测试代码为:

//10秒触发一次计时间隔
const int Interval = 10 * 1000;

//定义计时器
Timer timer = new Timer(Interval);
//计时器间隔触发事件
timer.Elapsed += (o, e) =>
{
    Console.WriteLine("计时器触发:" + DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"));
};
timer.Start();
Console.WriteLine("开始计时器:" + DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"));

//按下便是重置
Console.ReadKey();
Console.WriteLine("开始计时器重置");
timer.Stop();
timer.Interval = Interval;
timer.Start();
Console.WriteLine("重新开始开始计时器:" + DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"));

Console.ReadKey();
Console.WriteLine("开始计时器暂停恢复能否重置?");
timer.Stop();
timer.Start();
Console.WriteLine("重新开始开始计时器:" + DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"));

Console.ReadKey();
Console.WriteLine("直接Start一次能否重置?");
timer.Start();
Console.WriteLine("重新开始开始计时器:" + DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"));

Console.ReadKey();

运行结果:

可以看到,我们可以使用两种方式来对 Timer 进行重置:

1. 通过重新设置 Interval 的属性,然后再通过 Start 方法重新开启计时器(最后一种测试,只单纯使用 Start 方法开启计时器是没有效果的);

这边通过观察 Timer 的 源代码 156 行,可以看到 Interval 的内部会做一个刷新的操作,如下图:

 

2. 通过先 Stop 方法停止计时器,然后再使用 Start 方法进行重新开启;

!!那么问题来了,是不是对于所有不同程序集下的计时器都是这样的呢?!!

 答:并不是的,不同计时器有点不同。下面是分别对 System.Threading.Timer 、System.Windows.Threading.DispatcherTimer 以及 System.Windows.Forms 的测试。

① 对 System.Threading.Timer 的测试:

对于这个计时器,有一些坑可以在这篇文章中进行学习《 C# 工作总结(三):System.Threading.Timer 的回收问题 》。它并没有 Start 和 Stop 方法,也没有 Interval 属性,在构造函数中通过设置一些初始值之后,就会开始启动(有一个起始触发一次的延迟,可以看到每次启动之前先会步过这个事件先触发一次)。

测试代码:

//10秒触发一次计时间隔
const int Interval = 10 * 1000;

//定义计时器
Timer timer = new Timer((state) =>
{
    Console.WriteLine("计时器触发:" + DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"));
},
null, 0, Interval);
Console.WriteLine("开始计时器:" + DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"));

//按下便是重置
Console.ReadKey();
Console.WriteLine("开始计时器重置");
timer.Change(0, Interval);
Console.WriteLine("重新开始开始计时器:" + DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"));

Console.ReadKey();

运行结果:

可以看到我们只能通过使用 Change 的方法对这个计时器进行重置。

② 对 System.Windows.Threading.DispatcherTimer 的测试:

这个计时器是来自于 WPF 框架的计时器,它和  System.Timers.Timer 和 System.Threading.Timer 的区别在于,它是在 UI 线程中运行的,有点类似于 System.Windows.Forms.Timer 。在 UI 线程中使用的区别就是,对于 System.Windows.Threading.DispatcherTimer 和 System.Windows.Forms.Timer 在修改 UI 的时候,不需要使用 Invoke 或者是 BeginInvoke 来避免跨线程访问修改 UI 控件的问题。

经过测试发现,在 Console 中使用 System.Windows.Threading.DispatcherTimer 会失败(没有计时间隔触发,但也不抛出异常)。下面是一个测试:

新建一个窗体的 Loaded 加载事件,然后输入如下代码:

XAML 代码:

<Grid>
    <Grid.RowDefinitions>
        <RowDefinition Height="*"/>
        <RowDefinition Height="30"/>
    </Grid.RowDefinitions>
    <Grid.ColumnDefinitions>
        <ColumnDefinition Width="*"/>
        <ColumnDefinition Width="*"/>
        <ColumnDefinition Width="*"/>
    </Grid.ColumnDefinitions>

    <ListBox x:Name="listBox" Grid.ColumnSpan="3" />
    <Button x:Name="btn1" Grid.Row="1" Grid.Column="0" Content="Interval重置" Click="btn1_Click"/>
    <Button x:Name="btn2" Grid.Row="1" Grid.Column="1" Content="Stop与Start重置" Click="btn2_Click"/>
    <Button x:Name="btn3" Grid.Row="1" Grid.Column="2" Content="仅仅Start" Click="btn3_Click"/>
</Grid>

后台代码:

/// <summary>
/// MainWindow.xaml 的交互逻辑
/// </summary>
public partial class MainWindow : Window
{
    //定义计时器
    private DispatcherTimer timer = null;

    //10秒触发一次计时间隔
    private const int Interval = 10;

    public MainWindow()
    {
        InitializeComponent();
    }

    private void Window_Loaded(object sender, RoutedEventArgs e)
    {
        //定义计时器
        timer = new DispatcherTimer();
        timer.Interval = TimeSpan.FromSeconds(Interval);
        //计时器间隔触发事件
        timer.Tick += (_o, _e) =>
        {
            this.listBox.Items.Add("计时器触发:" + DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"));
        };
        timer.Start();
        this.listBox.Items.Add("开始计时器:" + DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"));
    }

    private void btn1_Click(object sender, RoutedEventArgs e)
    {
        //按下便是重置;
        this.listBox.Items.Add("开始计时器重置");
        timer.Stop();
        timer.Interval = TimeSpan.FromSeconds(Interval);
        timer.Start();
        this.listBox.Items.Add("重新开始开始计时器:" + DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"));
    }

    private void btn2_Click(object sender, RoutedEventArgs e)
    {
        this.listBox.Items.Add("开始计时器暂停恢复能否重置?");
        timer.Stop();
        timer.Start();
        this.listBox.Items.Add("重新开始开始计时器:" + DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"));
    }

    private void btn3_Click(object sender, RoutedEventArgs e)
    {
        this.listBox.Items.Add("直接Start一次能否重置?");
        timer.Start();
        this.listBox.Items.Add("重新开始开始计时器:" + DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"));
    }
}

运行结果:

可以看出对于 System.Windows.Threading.DispatcherTimer 来说,它的重置方法和 System.Timers.Timer 一样。

③ 对 System.Windows.Forms.Timer 的测试:

System.Windows.Forms.Timer 与 System.Windows.Threading.DispatcherTimer 的特点非常的类似,也是不能在 Console 控制台中运行。下面是测试代码:

public partial class Form1 : Form
{
    //定义计时器
    private Timer timer = null;

    //10秒触发一次计时间隔
    private const int Interval = 10 * 1000;

    public Form1()
    {
        InitializeComponent();
    }

    private void Form1_Load(object sender, EventArgs e)
    {
        timer = new Timer();
        timer.Interval = Interval;
        timer.Tick += (_o, _e) =>
        {
            this.listBox1.Items.Add("计时器触发:" + DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"));
        };
        timer.Start();
    }

    private void button1_Click(object sender, EventArgs e)
    {
        this.listBox1.Items.Add("开始计时器重置");
        timer.Stop();
        timer.Interval = Interval;
        timer.Start();
        this.listBox1.Items.Add("重新开始开始计时器:" + DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"));
    }

    private void button2_Click(object sender, EventArgs e)
    {
        this.listBox1.Items.Add("开始计时器暂停恢复能否重置?");
        timer.Stop();
        timer.Start();
        this.listBox1.Items.Add("重新开始开始计时器:" + DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"));
    }

    private void button3_Click(object sender, EventArgs e)
    {
        this.listBox1.Items.Add("直接Start一次能否重置?");
        timer.Start();
        this.listBox1.Items.Add("重新开始开始计时器:" + DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"));
    }
}

运行结果:

测试的结果和 System.Windows.Threading.DispatcherTimer 和 System.Timers.Timer 一致。

四. 没啥用的技巧

 为使用方法,我们可以使用 扩展方法 对着几个 Timer 进行一下扩展,这边以 System.Timers.Timer 来做一下扩展。

代码如下:

/// <summary>
/// 扩展方法类
/// </summary>
public static class Extensions
{
    /// <summary>
    /// 给 System.Timers.Timer 增加一个扩展方法 Restart 用于重新开始重置计时间隔
    /// </summary>
    /// <param name="timer"></param>
    /// <param name="invertal">重置计时间隔的长度</param>
    public static void Restart(this System.Timers.Timer timer, int invertal = 0)
    {
        //重新开始
        timer.Stop();

        if (invertal > 0)
        {
            timer.Interval = invertal;
        }
        else
        {
            //set <- get
            timer.Interval = timer.Interval;//利用内部的change方法
        }
            
        //重新开始
        timer.Start();
    }
}

引入这个静态的扩展类之后,我们就可以上端使用这个扩展方法:

//10秒触发一次计时间隔
const int Interval = 10 * 1000;

//定义计时器
Timer timer = new Timer(Interval);
//计时器间隔触发事件
timer.Elapsed += (o, e) =>
{
    Console.WriteLine("计时器触发:" + DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"));
};
timer.Start();
Console.WriteLine("开始计时器:" + DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"));

Console.ReadKey();
Console.WriteLine("开始计时器重置");

//调用
timer.Restart();
Console.WriteLine("重新开始开始计时器:" + DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"));

Console.ReadKey();

五. 失效的代码下载地址

下载代码

推荐阅读