一. 廢話
今天在做項目的時候遇到了如何重啟一個計時器的問題,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();