C# - 四种定时器详细介绍和使用方法
.Net中为程序员提供了四种定时器:
- System.Windows.Forms.Timer类型(Winfrom专用)
- System.Threading.Timer类型
- System.Timers.Timer类型
- System.Windows.Threading.DispatcherTimer类型(WPF专用)
这4种类型都实现了定时的功能。程序员通常需要做的是为定时器设置一个间断时间,设置定时器到时后的处理方法,然后可以等待定时器不断地计时和触发时间处理,下面详细介绍下这四种类型的特点:
1. System.Windows.Forms.Timer类型(Winfrom专用)
.net设计这个定时器的目的是为了方便程序员在Window Form中使用定时器。当一个System.Windows.Forms.Timer类被构造时,当前定时器会和当前线程进行关联。而当计时器的计时达到后,一个定时器消息将被插入到当前线程的消息队列中。当前线程逐一处理消息中的所有消息,并一一派发给各自的处理方法。这样的机制和利用工作者进程定时有很大的区别,事实上,System.Windows.Forms.Timer类型并没有涉及多线程的操作,定时器的设置、定时方法的执行都在同一个线程之上。
这就意味着System.Windows.Forms.Timer并不能准确计时,事实上,当消息阻塞时,定时器的误差将非常大,因为定时器消息只能等待在前面的所有消息处理完后才能得到处理。但是因为System.Windows.Forms.Timer类型的定时器并不涉及多线程的操作,因此是线程安全的,不会发生回调方法重入的问题。
使用步骤:
1. System.Windows.Forms.Timer myTimer = new System.Windows.Forms.Timer();//实例化
2. myTimer.Tick += new EventHandler(函数名); //给timer挂起事件
3. myTimer.Enabled = true;//使timer可用
4. myTimer.Interval = 1000; //设置时间间隔,以毫秒为单位,1000是1秒
5. myTimer.Stop(); //如果要暂停计时则使用Stop()方法
6. myTimer.Enabled = false;//若要停止使用timer,则使之不可用
//Winform自带的Timer控件使用
using System.Windows.Forms;
namespace WindowsFormsApp1
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
//初始化定时器
Timer timer = new Timer();
//定时器间隔1秒
timer.Interval = 1000;
//定时器触发事件,timer1_Tick是winform自带的Timer控件的方法
timer.Tick += timer1_Tick;
//定时器可用
timer.Enabled = true;
//启动定时器
timer.Start();
}
/// <summary>
/// winform自带的Timer定时器控件
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void timer1_Tick(object sender, EventArgs e)
{
richTextBox1.Text += DateTime.Now.Second.ToString() + "\n";
Console.WriteLine(richTextBox1.Text);
}
}
///用户自定的定时器方法
using System.Windows.Forms;
namespace WindowsFormsApp1
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
Timer timer = new Timer();
//定时器间隔1秒
timer.Interval = 1000;
timer.Tick += DefineTimer_Tick;
//定时器可用
timer.Enabled = true;
//启动定时器
timer.Start();
}
/// <summary>
/// 用户自定义的定时器方法,不用自带的Timer控件
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void DefineTimer_Tick(object sender, EventArgs e)
{
richTextBox1.Text += DateTime.Now.Second.ToString() + "\n";
Console.WriteLine(richTextBox1.Text);
}
}
Tips:
1. 在Winfrom中既可以使用Timer控件(这个控件也只是Tick时触发的方法等价于用户自定义的触发器方法,除此之外使用和自定义使用一样),也可以使用自定义的定时器方法
2. 它的主要缺点是计时不精确,而且必须有消息循环,Console Application(控制台应用程序)无法使用。
2. System.Threading.Timer类型
这个定时器类型的使用相对复杂,但同时它也是最优化的一个定时器类型。System.Threading.Timer的定时方法将被确定在工作者线程上执行。所有的对象有一个线程控制,当下一个定时到达时,该线程会负责在线程中获得一个新的工作者线程,用以执行相应的回调方法。
虽然这个定时器是相对最优化的一个定时器类型,但是从其机制上来讲,其并不是线程安全的,可能会出现回调方法重入的问题。解释下方法重入,是一个有关多线程编程的概念,意思大概是:程序中,多个线程同时运行时,就可能发生同一个方法被多个进程同时调用的情况。当这个方法中存在一些非线程安全的代码时,方法重入会斗志数据不一致的情况,这个非常严重的bug。
使用步骤:
1、实例构造一个线程定时器。
System.Threading.Timer mytimer =
new System.Threading.Timer(new System.Threading.TimerCallback(timerCall), null, 0, 1000);
2、编写timerCall回调函数
格式:private void timerCall(object xxxxx) { .......; .......;}
3、使用Change(Int32,Int32)方法来修改定时器参数实现停止、重新开始等。
4、使用Dispose()方法释放定时器资源。
using System;
using System.Threading;
class TimerExample
{
static void Main()
{
// Create an event to signal the timeout count threshold in the
// timer callback.
AutoResetEvent autoEvent = new AutoResetEvent(false);
StatusChecker statusChecker = new StatusChecker(10);
// Create an inferred delegate that invokes methods for the timer.
TimerCallback tcb = statusChecker.CheckStatus;
// Create a timer that signals the delegate to invoke
// CheckStatus after one second, and every 1/4 second
// thereafter.
Console.WriteLine("{0} Creating timer.\n",
DateTime.Now.ToString("h:mm:ss.fff"));
Timer stateTimer = new Timer(tcb, autoEvent, 1000, 250);
// When autoEvent signals, change the period to every
// 1/2 second.
autoEvent.WaitOne(5000, false);
stateTimer.Change(0, 500);
Console.WriteLine("\nChanging period.\n");
// When autoEvent signals the second time, dispose of
// the timer.
autoEvent.WaitOne(5000, false);
stateTimer.Dispose();
Console.WriteLine("\nDestroying timer.");
}
}
class StatusChecker
{
private int invokeCount;
private int maxCount;
public StatusChecker(int count)
{
invokeCount = 0;
maxCount = count;
}
// This method is called by the timer delegate.
public void CheckStatus(Object stateInfo)
{
AutoResetEvent autoEvent = (AutoResetEvent)stateInfo;
Console.WriteLine("{0} Checking status {1,2}.",
DateTime.Now.ToString("h:mm:ss.fff"),
(++invokeCount).ToString());
if(invokeCount == maxCount)
{
// Reset the counter and signal Main.
invokeCount = 0;
autoEvent.Set();
}
}
}
3. System.Timers.Timer类型
这是一个相对较旧的类型。它和System.Threading.Timer一样,可以由工作者线程来执行回调方法,但同时它也可以在IDE环境中被拖到窗体控件上,这个时候它的行为非常类似于System.Windows.Forms.Timer类型,在消息过多时其定时并不准确。
System.Timers.Timer可以视为System.Threading.Timer的一个包装,其类型设计相对古老,不建议使用该定时器。
using System.Timers;
namespace ConsoleApp1
{
class Program
{
static void Main(string[] args)
{
System.Timers.Timer timer = new Timer();
//1000毫秒,1秒
timer.Interval = 1000;
timer.Elapsed += new ElapsedEventHandler(OnTimedEvent);
timer.Start();
Console.ReadLine();
}
/// <summary>
/// OnTimedEvent方法的参数和ElapsedEventHandler保持一致
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
public static void OnTimedEvent(object sender, ElapsedEventArgs e)
{
var date = DateTime.Now.Second.ToString() + "\n";
Console.WriteLine(date);
}
}
4. WPF中的定时器
4.1 WPF后台调用前端UI页面
WPF界面是没有timer控件的,Winform有。WPF中可以使用的的两种定时器
第一种:DispatcherTimer定时器,可以说是专门为WPF界面设计的定时器。因为这个定时器是和UI都在同一线程上的。
第二种: System.Timers.Timer定时器,这种定时器是和UI非同一个线程。
C#有两种定时器,如果是写WPF上位机的话,自然是要用和UI同一个线程的定时器,不然要考虑跨线程的问题。
一般定时器间隔最多设置为1分钟,太长可能响应断开或者有问题
4.1.1 DispatcherTimer定时器
首先需要注意的是:在wpf中涉及到界面操作的话,一定要使用定时器DispatcherTime,DispatcherTimer是为wpf专门设计的,不然的话使用其他种类的定时器会提示界面资源被其他线程所拥有而无法更新界面。
使用方法步骤:
1. 引用命名空间:using System.Windows.Threading;
2. 实例化DispatcherTimer定时器
3. 在需要用定时器的地方里面设置定时器的一些参数
4. 在需要开始定时器的地方,开启定时器或者在固定的时间点开始定时器任务
/// <summary>
/// MainWindow是WPF的启动主窗口
/// </summary>
public MainWindow()
{
InitializeComponent();
DispatcherTimer timer = new DispatcherTimer();
//设置的间隔为一分钟,每间隔一分钟就扫描一次要触发执行的事件
timer.Interval = new TimeSpan(0, 1, 0);
//Tick触发事件,每隔一分钟扫一次
timer.Tick += new EventHandler(Fixedtimer_Tick);
timer.IsEnabled = true;
}
public static void Fixedtimer_Tick(object sender, EventArgs e)
{
//在每天的21:30分触发事件,每隔一分钟就扫一次,看有没有到21:30,到了就触发if里的事件,
//没到就继续每隔一分钟扫一下,监测有没有到21:30
if (DateTime.Now.Hour == 21 && DateTime.Now.Minute == 12)
{
//触发事件之后,弹出窗口PoPUp1和PoPUp2
var popwindow1 = new PoPUp1();
popwindow1.Show();
var popwindow2 = new PopUp2();
popwindow2.Show();
}
}
public static void timer_Tick()
{
DispatcherTimer timer = new DispatcherTimer();
//设置的间隔为一分钟,每间隔一分钟就扫描一次要触发执行的事件
timer.Interval = new TimeSpan(0, 1, 0);
//Tick触发事件,每隔一分钟扫一次
timer.Tick += new EventHandler(timerTickFunc);
timer.IsEnabled = true;
//在timer_Tick这个方法里就开启定时器开始计时了
timer.Start();
}
public static void timerTickFunc(object sender, EventArgs e)
{
//触发事件之后,弹出窗口PoPUp1
var popwindow1 = new PoPUp1();
popwindow1.Show();
}
注意:
1. EventHandler是定时器回调函数,它是根据定时器的要求间隔去调用EventHandler(Fixedtimer_Tick)括号中的Fixedtimer_Tick函数
2. 定时器Start()以后,也就是启动以后,要记得及时关上!(timer.Stop())否则会一直执行下去的!例如这里在固定的时间点启动了Fixedtimer_Tick定时器,没有关闭,它就会每天在这个固定时间点执行Fixedtimer_Tick函数
4.1.2 System.Timers.Timer定时器(WPF中不建议使用)
用法步骤:
1. 引用命名空间:using System.Timers;
2. 实例化System.Timers.Timer定时器
3. 设置定时器的一些参数
4. 在需要开始定时器的地方,开启定时器(timer.Start())
5. 会报错:使用SAT线程,防止出现:调用的线程必须为SAT,因为许多UI组件都需要
解放方案:用下面代码的application.current
public static void TimerFunc()
{
//实例化System.Timers.Timer定时器
System.Timers.Timer timer = new System.Timers.Timer();
//设置定时器的一些参数
//这里设置的间隔时间为1分钟
timer.Interval = 60000;
timer.Elapsed += new ElapsedEventHandler(TimerElapsedFunc);
}
public static void TimerElapsedFunc(object sender, ElapsedEventArgs e)
{
//在每天的21:30分触发事件,每隔一分钟就扫一次,看有没有到21:30,到了就触发if里的事件,
//没到就继续每隔一分钟扫一下,监测有没有到21:30
if (DateTime.Now.Hour == 21 && DateTime.Now.Minute == 58)
{
Application.Current.Dispatcher.Invoke(new Action(()=>{
//另一个线程开启中
//触发事件之后,弹出窗口PoPUp1和PoPUp2
var popwindow1 = new PoPUp1();
popwindow1.Show();
var popwindow2 = new PopUp2();
popwindow2.Show();
}));
}
}
在WPF中也可以用,但是有时候WPF运行一个窗口,在使用定时器打开其他窗口或者其他线程时(相当于:一个线程同时做两件事,派一个人不能同时去执行多个事,不能一心二用的意思吧。)会报错:调用线程无法访问此对象,因为另一个线程拥有该对象
解决方案: 在一个Timers事件里面,让它去异步执行,问题就可以解决
Dispatcher:Dispatcher是WPF管理控件线程的方式。
Task.Factory.StartNew(() =>
{
this.Dispatcher.Invoke(new Action(() =>
{
txtTestThread.Text = "另一个线程开启中";
}));
Thread.Sleep(4000);
this.Dispatcher.Invoke(new Action(() =>
{
txtTestThread.Text = "线程结束,延迟4秒";
}));
});
Dispatcher有两个方法,Invoke和BeginInvoke,网上的说法是:Invoke是同步调用,直到UI线程实际执行完该委托它才返回,而BeginInvoke是异步调用,会立即返回。
//BeginInvoke方法和Invoke方法的区别
//控制台会等到4秒后才输出test
new Thread(() => {
Application.Current.Dispatcher.Invoke(new Action(() => {
Thread.Sleep(4000);
}), null);
Console.WriteLine("test");
}).Start();
//控制台在一开始就输出test
new Thread(() => {
Application.Current.Dispatcher.BeginInvoke(new Action(() => {
Thread.Sleep(4000);
}), null);
Console.WriteLine("test");
更多推荐
所有评论(0)