計時器小程序——由淺入深實例講解


  本菜在實現簡單的計時器過程中遇到問題的一些成長筆記,有不完整觀點的話請多多見諒,也看了眾多大神的博客才整理的筆記,下面來實現個人寫的小程序。

首先第一個實例(很簡單):

  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

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM