在.NET Framework里面提供了三种Timer:
① System.Windows.Forms.Timer
(运行在主线程上,通过Tick事件触发)
② System.Timers.Timer
(可以多线程,也可以单线程,通过Elapsed事件触发)
③ System.Threading.Timer
- System.Windows.Forms.Timer:基于窗体程序,直接拖拽到窗体上。
属性:
Interval:间隔触发的时间。
Enabled:true表示启用,false表示禁止
private void WriteInfo(string str)
{
if (txt_Info.InvokeRequired)
{
this.Invoke(new Action(() => { txt_Info.AppendText(str + "\r\n"); }));
}
else
{
txt_Info.AppendText(str + "\r\n");
}
}
private void btn_Start_Click(object sender, EventArgs e)
{
if (btn_Start.Text == "开始")
{
btn_Start.Text = "停止";
Application.DoEvents();
timer1.Enabled = true;
btn_Start.Text = "开始";
}
}
private void timer1_Tick(object sender, EventArgs e)
{
for (int i = 0; i < 10; i++)
{
Thread.Sleep(1000);
WriteInfo("Count: " + i + "\t id:" + Thread.CurrentThread.ManagedThreadId);
}
}
结果:
总结:
- 从上面的运行结果可以看到,窗体Timer 都是运行在同一个线程上主线程),所以会卡UI 。
- 单线程执行的优点就是,不需要关心线程安全,也就是说,一段程序从头至尾都是由一个线程完成。
- Form下的Timer用于对时间精度要求不高的地方。
2. System.Timers.Timer:只能通过声明对象的方式使用,用于对时间精度有要求的地方。
属性:
AutoReset:true表示事件关联的方法只执行一次,false表示一直执行。
SynchronizingObject:此timer默认是运行在线程池的,也就是说CPU自动分配线程,但是我们可以使用SynchronizingObject属性指定唯一线程。
private System.Timers.Timer tmr = new System.Timers.Timer();
private object obj = new object();
public Timer_timer()
{
InitializeComponent();
tmr.Elapsed += new System.Timers.ElapsedEventHandler(OnTmrTrg);
tmr.Interval = 100;
tmr.AutoReset = true; //true-一直循环 ,false-循环一次
// tmr.SynchronizingObject = this; //运行在主线程上
tmr.Enabled = false;
}
private void OnTmrTrg(object sender, System.Timers.ElapsedEventArgs e)
{
WriteInfo("tmr loop: " + Thread.CurrentThread.ManagedThreadId);
lock (obj)
{
WriteInfo("tmr is running :" + Thread.CurrentThread.ManagedThreadId);
for (int i = 0; i < 10; i++)
{
Thread.Sleep(1000);
WriteInfo("Count: " + i + ": " + Thread.CurrentThread.ManagedThreadId);
}
}
}
private void WriteInfo(string str)
{
if (txt_Info.InvokeRequired)
{
this.Invoke(new Action(() => { txt_Info.AppendText(str + "\r\n"); }));
}
else
{
txt_Info.AppendText(str + "\r\n");
}
}
private void btn_Enter_Click(object sender, EventArgs e)
{
if (btn_Enter.Text == "确定")
{
btn_Enter.Text = "取消";
Application.DoEvents();
tmr.Start();
}
else
{
btn_Enter.Text = "确定";
tmr.Stop();
}
}
结果:在未指定SynchronizingObject属性的时候默认是多线程运行的,运行如图:
通过 SynchronizingObject = this 将指定主线程,运行结果如下
总结:
- Timer.timer是通过主线程创建,执行在线程池上的,所以默认在不指定SynchronizingObject属性的情况下,不卡主界面。
- 由于可以多线程执行,所以需要考虑线程安全(假设一种情况,A线程正在计算余额(总金额-消费金额),这个时候然后B线程将总金额改了,那A线程所返回的会发生错误)。解决方案有两种:1. 金额的计算工作全部由A线程执行(单线程)。2. 使用Lock () 确保每次A线程执行计算任务的时候B线程等待A完成在进行。
- Timer.timer可以通过AutoReset = false 设置,只执行一次事件方法。
- Start ()&Stop () 和 Enabled = true/false等效。
System.Threading.Timer:同样是运行在线程池,但是此timer是以回调的方式来实现定时任务,
public partial class Threading_Timer : Form
{
public System.Threading.Timer tmr;
public static int count = 0;
public Threading_Timer()
{
InitializeComponent();
}
private void btn_Start_Click(object sender, EventArgs e)
{
if (btn_Start.Text == "开始")
{
btn_Start.Text = "结束";
tmr = new System.Threading.Timer(new System.Threading.TimerCallback((obj) =>
{
Tmp tmp = (Tmp)obj;
WriteInfo(tmp.info + ":" + count++);
}), new Tmp() { info = "Count: " }, 0, 1000);
}
else
{
btn_Start.Text = "开始";
tmr.Dispose();
}
}
private void WriteInfo(string str)
{
if (txt_Info.InvokeRequired)
{
this.Invoke(new Action(() => { txt_Info.AppendText(str + "\r\n"); }));
}
else
{
txt_Info.AppendText(str + "\r\n");
}
}
}
public class Tmp
{
public string info { get; set; }
}
运行结果:
总结:
- 运行于线程池,不卡主界面。
- 没有Start () 和 Stop () 方法控制启动停止。在对象构造的时候启动,需要使用Dispose () 方法停止执行。
- 构造函数参数列表: new timer ( new TimerCallback ( callBack), 回调函数传参对象 , 启动时间 , 间隔时间)。