C#中的線程三 (結合ProgressBar學習Control.BeginInvoke)


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", nullnull)    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控件!!!!,這是上篇文章所沒有提到的!


免責聲明!

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



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