本菜在實現簡單的計時器過程中遇到問題的一些成長筆記,有不完整觀點的話請多多見諒,也看了眾多大神的博客才整理的筆記,下面來實現個人寫的小程序。
首先第一個實例(很簡單):
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