程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> .NET網頁編程 >> C# >> C#入門知識 >> C# 一個簡單的秒表引發的窗體卡死問題

C# 一個簡單的秒表引發的窗體卡死問題

編輯:C#入門知識

一個秒表程序也是我的一個心病,因為一直想寫這樣的一個東西,但是總往GUI那邊想,所以就比較怵,可能是上學的時候學MFC搞出的後遺症吧,不過當我今天想好用Win Form(話說還是第一次寫win form)寫這麼一個東西的時候,居然so easy。   所以說,做不了不可怕,怕的是你不去做,因為你不去做,你就永遠不知道你能不能做它。事實證明,大部分你猶豫能不能做的事情,實際上你都能搞定。   雖然成功實現了一個秒表的簡單功能,即開始計時和停止。但是卻引發了一個關於win form和C#線程的問題。   下面一個一個來,先說一下秒表的類實現   namespace Utils {     public class Time     {         private int _minute;         private int _second;         private bool _flag;//線程標識         private Thread _TimingThread = null;           public Time()         {             this._minute = 0;             this._second = 0;             this._flag = true;         }         /// <summary>         /// 開始計時         /// </summary>         public void Start()         {             if (_TimingThread == null)             {                 _TimingThread = new Thread(new ThreadStart(AddSecond));                 _TimingThread.Start();             }         }         /// <summary>         /// 線程執行方法         /// </summary>         private void AddSecond()         {             while(_flag)             {                 Thread.Sleep(1000);                 if (this._second == 59)                 {                     this._minute++;                     this._second = 0;                 }                 else                 {                     this._second++;                 }             }         }         /// <summary>         /// 格式化顯示計時結果         /// </summary>         /// <returns></returns>         public string FormatTimeResult()         {             string minute = string.Empty;             string second = string.Empty;             if (this._minute < 10)             {                 minute = "0" + this._minute.ToString();             }             else              {                 minute = this._minute.ToString();             }             if (this._second < 10)             {                 second = "0" + this._second.ToString();             }             else             {                 second = this._second.ToString();             }             return minute + ":" + second;         }         /// <summary>         /// 停止         /// </summary>         public void Stop()         {             this._flag = false;         }         /// <summary>         /// 歸0操作         /// </summary>         public void Zero()         {             this._minute = 0;             this._second = 0;         }     } } 秒表的實現還是比較簡單的,感覺這樣寫,也方便以後做擴展。   下面說說win form方面   窗體就是這樣,一個label,兩個button   QQ圖片20140613105209   最開始,我寫了這樣一段代碼       public partial class Form1 : Form     {         private Time mTime = null;         private Thread mDisplayThread = null;         public Form1()         {             InitializeComponent();             mTime = new Time();//實例化秒表類         }         private void button_start_Click(object sender, EventArgs e)         {             mTime.Start();             mDisplayThread = new Thread(new ThreadStart(DisplayCurrentTime));             mDisplayThread.Start();             button_start.Enabled = false;         }           public void DisplayCurrentTime()         {             while (true)             {                 Thread.Sleep(1000);                 label_Time.Text = mTime.FormatTimeResult();//對Label標簽進行實時更新                 Console.WriteLine("{0}", mTime.FormatTimeResult());             }         }         private void button_stop_Click(object sender, EventArgs e)         {             mTime.Stop();             button_start.Enabled = true;         } } 這樣寫感覺思路上沒什麼問題,當點擊【開始計時】按鈕的同時創建一個線程,而這個線程是用來每隔一秒去更新一下label上的顯示計時時間。   然而,之後卻報一個這樣的錯誤:Cross-thread operation not valid: Control 'label_Time' accessed from a thread other than the thread it was created on.   網上查了一下,這個錯誤貌似很常見,MSDN上也給了一個出現此錯誤的原因,是這樣說的,當您試圖從單獨的線程更新一個win form時,會出現這個錯誤。   查了一下,就是說win form上的控件屬性想要進行修改的時候,只能在創建Control的線程裡調用,不能在以外的線程被調用。而上面的   label_Time.Text = mTime.FormatTimeResult(); 這段代碼呢恰恰是發生在新創建的線程之中,所以就會報錯了。   解決辦法是用delegate(委托)加上control.Invoke去聯合實現。下面看看實現部分       public partial class Form1 : Form     {         private Time mTime = null;         private Thread mDisplayThread = null;         public delegate void UpdateLabel();//聲明一個委托         public UpdateLabel updateLabel;//定義一個委托           public Form1()         {             InitializeComponent();             mTime = new Time();             updateLabel = new UpdateLabel(UpdateTime);//實例化一個委托對象         }           private void button_start_Click(object sender, EventArgs e)         {             mTime.Start();             mDisplayThread = new Thread(new ThreadStart(DisplayTimeFunc));             mDisplayThread.Start();             button_start.Enabled = false;           }         /// <summary>         /// 線程執行方法         /// </summary>         public void DisplayTimeFunc()         {             while (true)             {                 Thread.Sleep(1000);                 this.Invoke(this.updateLabel);             }         }         /// <summary>         /// 單獨對Label進行刷新         /// </summary>         public void UpdateTime()         {             label_Time.Text = mTime.FormatTimeResult();         }           private void button_stop_Click(object sender, EventArgs e)         {             mTime.Stop();             button_start.Enabled = true;         }       } 這段代碼裡mDisplayThread線程執行了DisplayTimeFunc方法,而DisplayTimeFunc方法裡實際就是在更新label,不同的是使用了Control.Invoke方法,上面不是說對控件屬性的更改要在創建控件的線程裡才執行嗎?現在看起來好像還是老樣子。那是因為我們不了解Control.Invoke是什麼東東。MSDN上的解釋是:在擁有此控件的基礎窗口句柄的線程上執行指定的委托。OK,明白了,this.updateLabel這個委托最後還是在窗口創建的線程中執行的。   回頭想想,其實思路也比較簡單,就是先將更改控件屬性的操作放在一個方法裡,然後寫個委托,再寫個線程,在線程的執行方法中調用這個委托就OK啦。   不過到這還不算全完,還有一個小問題,就是當我計時之後,想要關閉這個窗體的時候,發現又開始報錯了:   Invoke or BeginInvoke cannot be called on a control until the window handle has been created.   研究了一下發現了出現此問題的原因,就是我們“上完廁所沒有擦PP”,上面的代碼中沒有一個操作是對 mDisplayThread 這個線程做了終止的動作。   所以我們還需要添加以下動作           private void Form1_FormClosing(object sender, FormClosingEventArgs e)         {             mDisplayThread.Abort();         } 這樣就完整了,在關閉Form1窗體之前,先把線程終止。   做這個小東西的時候居然連帶著讓我了解了一些委托和Control.Invoke以及線程的知識點。我會找個時間好好把這部分看看的,爭取能總結點什麼出來。

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved