C#中的線程三(結合ProgressBar學習Control.BeginInvoke)
本篇繼上篇轉載的關於Control.BeginInvoke的論述之后,再結合一個實例來說明Cotrol.BeginInvoke的功能
通過前面2篇的學習應該得出以下結論
1、Delegate.BeginInvoke中執行的方法是異步的
1 public static void Start2() 2 { 3 Console.WriteLine("main thread:{0},{1},{2}", Thread.CurrentThread.CurrentCulture, Thread.CurrentThread.Name, Thread.CurrentThread.ManagedThreadId); 4 //DoSomethingDelegate del = new DoSomethingDelegate(Method1); 5 DoSomethingDelegate del = Method1; 6 del.BeginInvoke("this is delegate method", null,null) Console.WriteLine("main thread other things..."); 7 }
相當於另開了一個線程來執行Method1方法
2. 如果在UI線程里做Control.BeginInvoke,執行到的方法並沒有做到異步
1 private void butBeginInvoke_Click(object sender, EventArgs e) { 2 //A代碼段....... 3 this.BeginInvoke(new BeginInvokeDelegate(BeginInvokeMethod)); 4 //B代碼段...... 5 }
也就是說此段代碼里,BeginInvokeMethod方法並沒有異步執行到,也即沒有新開線程做BeginInvokeMethod這個方法
3.如果要讓Control.BeginInvoke做到異步,需要在UI線程里新開一個線程,在這個新開的線程里調用Control.BeginInvoke,才能有異步功能
1 private Thread beginInvokeThread; 2 private delegate void beginInvokeDelegate(); 3 private void StartMethod(){ 4 //C代碼段...... 5 Control.BeginInvoke(new beginInvokeDelegate(beginInvokeMethod)); 6 //D代碼段...... 7 } 8 private void beginInvokeMethod(){ 9 //E代碼段 10 } 11 private void butBeginInvoke_Click(object sender, EventArgs e) { 12 //A代碼段....... 13 beginInvokeThread = new Thread(new ThreadStart(StartMethod)); 14 beginInvokeThread .Start(); 15 //B代碼段...... 16 }
有了上面的理解,我們結合實例來繼續學習Control.BeginInvoke
二、ProgressBar的使用
WinForm里有這樣一種場景,就是一邊處理數據,一邊用ProgressBar顯示進度,要做出這種功能來如果還是用單線程那種普通的思路,按順序編碼下來,進度條就會從0突然變成100,失去了進度功能。
1.假如我們現在要讀取大量數據, 需要用ProgressBar來顯示進度,我們先定義一個類,此類中有一個方法Work是用來讀取數據的!
1 public class ProgressBarWork 2 { 3 public void Work() 4 { 5 int iTotal = 50;//計算工作量 6 int iCount = 0; 7 for (int i = 0; i < iTotal; i++) 8 { 9 System.Threading.Thread.Sleep(6);//模擬工作 iCount = i; 10 } 11 } }
繼續完善此方法,這個方法雖然完成了讀取數據的任務,但是如何讓外部的ProgressBar知道此方法執行的狀態呢?
答案是:該方法從開始執行,中間每次讀取,執行完畢要暴露出3個事件來,通知在這三種狀態下,ProgressBar應該如何顯示,為此我們要聲明一個委托,三個事件!
既然要用到事件,還需要用到自定義的EventArgs來傳遞狀態,為此我們定義一個EventArgs
1 public class WorkEventArgs : EventArgs 2 { 3 //主要是用來往外傳遞信息的 4 public WorkStage Stage; 5 public string Info = ""; 6 public int Position = 0; 7 public WorkEventArgs(WorkStage Stage, string Info, int Position) 8 { 9 this.Stage = Stage; 10 this.Info = Info; 11 this.Position = Position; 12 } 13 } 14 public enum WorkStage 15 { 16 BeginWork, //准備工作 17 DoWork, //正在工作 18 EndWork, //工作結束 19 }
接着在ProgressBarWork類中定義事件
1 public delegate void PBWorkEventHandler(object sender, WorkEventArgs e); 2 public class ProgressBarWork 3 { 4 //開始事件 5 public event PBWorkEventHandler OnStartWorkEvent; 6 //執行事件 7 public event PBWorkEventHandler OnDoWorkEvent; 8 //結束事件 9 public event PBWorkEventHandler OnEndWorkEvent; 10 11 public void Work() 12 { 13 int iTotal = 50;//計算工作量 14 int iCount = 0; 15 SendEvents(new WorkEventArgs(WorkStage.BeginWork, "start work", iTotal)); 16 for (int i = 0; i < iTotal; i++) 17 { 18 System.Threading.Thread.Sleep(6);//模擬工作 19 iCount = i; 20 SendEvents(new WorkEventArgs(WorkStage.DoWork, "working" + iCount.ToString(), iCount)); 21 } 22 SendEvents(new WorkEventArgs(WorkStage.EndWork, "end work", iTotal)); 23 } 24 25 private void SendEvents(WorkEventArgs e) 26 { 27 switch (e.Stage) 28 { 29 case WorkStage.BeginWork: 30 if (OnStartWorkEvent != null) OnStartWorkEvent(this, e); 31 break; 32 case WorkStage.DoWork: 33 if (OnDoWorkEvent != null) OnDoWorkEvent(this, e); 34 break; 35 case WorkStage.EndWork: 36 if (OnEndWorkEvent != null) OnEndWorkEvent(this, e); 37 break; 38 default: 39 if (OnDoWorkEvent != null) OnDoWorkEvent(this, e); 40 break; 41 42 } 43 }
這樣就在"方法執行前","執行中","執行結束"這三種狀態下暴露了三個不同的事件!我們要在這三個不同的事件下,控制ProgressBar的狀態
拿"方法執行前"來說,我們需要在UI線程中做如下編碼
1 ProgressBarWork work = new ProgressBarWork(); 2 //訂閱事件 3 work.OnStartWorkEvent += new PBWorkEventHandler(WorkStart); 4 //其他事件訂閱...
然后調用work.Work()方法來開始讀取數據,我們通過前面的學習可以得知,如果要做到ProgressBar的顯示和數據處理同步,必須單獨開一個線程來做數據處理
1 System.Threading.ThreadStart startWork = work.Work; 2 System.Threading.Thread thread = new System.Threading.Thread(startWork); 3 thread.Start();
這樣在WorkStart方法中就可以設置ProgressBar中的初始值了
1 void WorkStart(object sender, WorkEventArgs e) 2 { 3 this.statusProgressBar.Maximum = e.Position; 4 this.statusProgressBar.Value = 0; 5 }
如果按照單線程的思想,這樣編碼是沒有問題的,但是如果運行這段程序,會拋出如下異常!
什么原因?這里的this.statusProgressBar是不會運行成功的,因為這個方法不是有UI線程來調用的,必須通過control.Invoke來給ProgressBar賦值!
1 void WorkStart(object sender, WorkEventArgs e) 2 { 3 PBWorkEventHandler del = SetMaxValue; 4 this.BeginInvoke(del, new object[] { sender, e }); 5 } 6 private void SetMaxValue(object sender, WorkEventArgs e) 7 { 8 this.statusProgressBar.Maximum = e.Position; 9 this.statusProgressBar.Value = 0; 10 }
這樣就確保了SetMaxValue方法是在UI線程中執行的
UI界面完整代碼如下:
1 private void ImitateProgressBar() 2 { 3 ProgressBarWork work = new ProgressBarWork(); 4 //訂閱事件 5 work.OnStartWorkEvent += new PBWorkEventHandler(WorkStart); 6 work.OnDoWorkEvent += new PBWorkEventHandler(Working); 7 work.OnEndWorkEvent += new PBWorkEventHandler(WorkEnd); 8 System.Threading.ThreadStart startWork = work.Work; 9 System.Threading.Thread thread = new System.Threading.Thread(startWork); 10 thread.Start(); 11 } 12 13 void WorkStart(object sender, WorkEventArgs e) 14 { 15 PBWorkEventHandler del = SetMaxValue; 16 this.BeginInvoke(del, new object[] { sender, e }); 17 18 } 19 private void SetMaxValue(object sender, WorkEventArgs e) 20 { 21 this.statusProgressBar.Maximum = e.Position; 22 this.statusProgressBar.Value = 0; 23 } 24 void Working(object sender, WorkEventArgs e) 25 { 26 PBWorkEventHandler del = SetNowValue; 27 this.BeginInvoke(del, new object[] { sender, e }); 28 } 29 private void SetNowValue(object sender, WorkEventArgs e) 30 { 31 this.statusProgressBar.Value = e.Position; 32 } 33 34 void WorkEnd(object sender, WorkEventArgs e) 35 { 36 PBWorkEventHandler del = SetEndValue; 37 this.BeginInvoke(del, new object[] { sender, e }); 38 } 39 private void SetEndValue(object sender, WorkEventArgs e) 40 { 41 this.statusProgressBar.Value = e.Position; 42 43 }
其實在以上代碼中,我們還可以通過this.Invoke(del, new object[] { sender, e });來調用,效果是跟this.BeginInvoke(del, new object[] { sender, e });調用時一樣的,因為
1 void WorkStart(object sender, WorkEventArgs e) 2 { 3 PBWorkEventHandler del = SetMaxValue; 4 this.Invoke(del, new object[] { sender, e }); 5 6 }
這個方法已經是在新開的線程上執行的,只要確保在新的線程上利用UI線程去給ProgressBar賦值即可
三、在這個例子中我們還可以看出Control.Invoke和Control.BeginInvoke這兩個方法的重要功能:
是在多線程的環境下 確保用UI線程去執行一些調用控件的方法,因為其他線程無法訪問UI控件!!!!,這是上篇文章所沒有提到的!