本菜在实现简单的计时器过程中遇到问题的一些成长笔记,有不完整观点的话请多多见谅,也看了众多大神的博客才整理的笔记,下面来实现个人写的小程序。
首先第一个实例(很简单):
winform窗体包含两个控件:label、button控件,点击控件开始计时,代码如下:
namespace Timer_Test { public partial class CommonInstance : Form { private static int startTime = 0; public CommonInstance() { InitializeComponent(); } private void btn_Start_Click(object sender, EventArgs e) { GetTimes(); //方法在外部封装,调用即可,你应该知道封装的好处咯 } //写个方法,用于计时运算,不能是静态方法,static和this不能共存; private void GetTimes() { startTime += 1; this.lb_getTimes.Text = Convert.ToString(startTime); } } }
哈哈,这个很简单实现吧,那么怎样才能让计时器自动计时呢,相信你已经想到timer控件,没错接下来实现下!
实现代码如下:
namespace Timer_Test { public partial class CommonInstance : Form { private static int startTime = 0; public CommonInstance() { InitializeComponent(); } private void btn_Start_Click(object sender, EventArgs e) { //GetTimes(); timer1.Start(); } private void timer1_Tick(object sender, EventArgs e) { startTime += 1; lb_getTimes.Text = startTime.ToString(); } private void CommonInstance_Load(object sender, EventArgs e) { //窗体加载时设置好timer的Enable属性为true:可用 timer1.Enabled = true; timer1.Interval = 1000; //设置间隔时间为1s } } }
其实你会发现,使用timer控件实现也很简单嘛,很多工作都是timer自己做了,省事多了,但我写这编文字的重点不仅仅这些,下面来说下重点吧:
多线程实现的计时器,不用timer控件了,自己写个机制实现它(这会让我们学到更多的知识),老实说,我还是第一次接触线程,刚开始真的是摸
不着头脑咋用来着捏,下面就细细说来.....
使用线程前,先引入命名空间:using System.Threading; 具体代码如下:
namespace Timer_Test { public partial class CommonInstance : Form { private static int startTime = 0; Thread thread = null;//声明一个线程 public CommonInstance() { InitializeComponent(); } private void btn_Start_Click(object sender, EventArgs e) { //GetTimes(); //timer1.Start(); thread = new Thread(new ThreadStart(GetTimes)); //创建开始线程实例,将要执行的方法作为参数 thread.Start(); } private void GetTimes() { startTime += 1; this.lb_getTimes.Text = Convert.ToString(startTime); } }
这时你就会暗暗自喜,线程也不过如此嘛,当你点击开始计时后就报错了哦,报错是你给lable控件赋值之时,为什么会错呢,因为你跨线程给UI界面控件赋值了,这关系
到数据的安全问题。
c#中禁止跨线程直接访问控件,InvokeRequired是为了解决这个问题而产生的当一个控件的InvokeRequired属性值为真时,说明有一个创建它以外的线程想访问它。此时它将会在内部调用new MethodInvoker(LoadGlobalImage)来完成下面的步骤,这个做法保证了控件的安全,你可以这样理解,有人想找你借钱,他可以直接在你的钱包中拿,这样太不安全,因此必须让别人先要告诉你,你再从自己的钱包把钱拿出来借给别人,这样就安全了。
这样就可以很好的理解上面那个winform程序中 this.BeginInvoke(fc);这行code了,这个执行后其实也就是主线程在调用fc中绑定的方法ThreadFuntion()了,这种方式其实相当于把这个新开的线程“注入”到了主控制线程中,它取得了主线程的控制。只要这个线程不返回,那么主线程将永远都无法响应。就算新开的线程中不使用无限循环,使可以返回了。这种方式的使用多线程也失去了它本来的意义。(copy过来的,源地址在最后面)
解决方法一:在点击事件里加这句代码:Control.CheckForIllegalCrossThreadCalls = false; // 默认为true;
意思是:不检查跨线程,这是不安全的,不推荐使用;
解决方法二:(这篇文章的意义所在了)
通常的方法是使用委托delegate委托主线程处理(解释是后面的啰嗦),和BeginInvoke()方法、IsBackground属性值(默认为false:即非后台线程)以及属
性InvokeRequired;
下面就要用到InvokeRequired这个propety
解决问题代码如下:
namespace Timer_Test { public partial class CommonInstance : Form { private delegate void FlushClient(); //定义一个委托代理,前面要和委托代理函数签名一致 private static int startTime = 0; Thread thread = null;//声明一个线程 public CommonInstance() { InitializeComponent(); } private void btn_Start_Click(object sender, EventArgs e) { //GetTimes(); //timer1.Start(); thread = new Thread(new ThreadStart(GetTimes)); thread.IsBackground = true; //设置为后台线程 thread.Start(); } //写个方法,用于计时运算,不能是静态方法,static和this不能共存; private void GetTimes() { while (true) //无限循环 { Thread.Sleep(1000); //睡眠时间为一秒 ThreadFunction(); } } //写个委托代理函数 private void ThreadFunction() { if (this.InvokeRequired) //等待异步 { FlushClient fc = new FlushClient(ThreadFunction);//实例化委托 this.BeginInvoke(fc); //通过代理刷新UI } else { startTime += 1; this.lb_getTimes.Text = Convert.ToString(startTime); } }
这里我就啰嗦一下咯........
怎么说呢:这时你应该弄清楚 线程 是如何工作的?
在C#中,非主线程(即非UI线程,就是通过new Thread创建的线程)是不能直接操作UI元素的,必须通过Handler与UI线程通讯,通知UI线程更新.而C#则采用委托的方式更
新UI元素。
线程分为前台线程和后台线程,线程默认为前台线程(主线程),这意味着任何前台线程在运行都会保持程序存活。
后台线程:只要有一个前台线程在运行,应用程序的进程就在运行。如果多个前台线程在运行,而Main()方法结束了,应用程序的进程就是激活的,直到所有前台线程完成其任务为止。
前台线程和后台线程的唯一的区别是— 后台线程不会阻止进程终止。
在默认情况下,用Thread 类创建的线程都是前台线程。线程池中的线程总是后台线程。
参考:http://www.cnblogs.com/Linford-Xu/archive/2012/09/19/2693340.html