最近一個項目需求中的一個功能是需要用progressBar反映處理文件的進度。
研究了Invoke和BeginInvoke方法。
Control.Invoke 方法 (Delegate) :在擁有此控件的基礎窗口句柄的線程上執行指定的委托。
Control.BeginInvoke 方法 (Delegate) :在創建控件的基礎句柄所在線程上異步執行指定委托。
我開始的想法是開一個線程處理,代碼如下:
private delegate void DoDataDelegate(object number); private void button2_Click(object sender, EventArgs e) { // Thread myThread = new Thread(DoData); Thread myThread = new Thread(new ParameterizedThreadStart(DoData)); myThread.IsBackground = true; myThread.Start(int.Parse(textBox1.Text)); //線程開始 Console.WriteLine("主線程繼續執行---------------"); } private void DoData(object number) { if (progressBar1.InvokeRequired) { Console.WriteLine("開始"); DoDataDelegate d = DoData; progressBar1.BeginInvoke(d, number); //代碼段D } else { progressBar1.Maximum = (int)number; Console.WriteLine("准備進行循環調用"); for (int i = 0; i < (int)number; i++) { //這里是一段耗時長的代碼 progressBar1.Value = i + 1; } } }
在上述代碼中當執行到progressBar1.BeginInvoke(d, number);時,myThread封送消息給UI,然后自己繼續執行代碼段D,代碼段D的執行相對於myThread是異步的。如果將
progressBar1.BeginInvoke(d, number);換成progressBar1.Invoke(d, number);其余的代碼不變,那么代碼段D將會等到線程myThread結束后才會執行。無論是調用BeginInvoke或者Invoke,因為調用者是progressBar1,所以更新progressBar1.Value時,是在"擁有此控件的基礎窗口句柄的線程(UI線程)上執行指定的委托",在更新value值之前有一段耗時長的代碼,所以此時UI線程會阻塞,處於假死狀態。
我的解決辦法是使用BackgroundWorker 類。
backgroundWorker1.WorkerReportsProgress = true; backgroundWorker1.DoWork += DoWork_Handler; backgroundWorker1.ProgressChanged += ProgressChanged_Handler; private void button2_Click(object sender, EventArgs e) { backgroundWorker1.RunWorkerAsync(); } private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e) { for (int i = 1; i <= 100; i++) { //這里省略一段執行耗時的操作
backgroundWorker1.ReportProgress(i); } } private void backgroundWorker1_ProgressChanged(object sender, ProgressChangedEventArgs e) { // Change the value of the ProgressBar to the BackgroundWorker progress. progressBar1.Value = e.ProgressPercentage; }
BackgroundWorker.RunWorkerAsync是創建了一個線程處理耗時的操作,backgroundWorker1_DoWork方法執行,執行到backgroundWorker1.ReportProgress(i);會觸發backgroundWorker1_ProgressChanged方法的執行,在backgroundWorker1_ProgressChanged方法中,更新value的值。這樣做UI界面和線程處理好使的工作是異步的,所以不會造成UI界面卡死的現象。
我了解了Invoke 和BeginInvoke的原理,但不知道怎么用他們實現更新ProgressBar的值,並保證UI界面不出現假死。所以我選擇了BackgroundWorker解決了我的問題。
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
再次更新。
經過和老師的討論,我自己進行了實踐。如果用ProgressBar反映處理耗時的程序的進度。可以開一個子線程處理耗時的部分,然后設置一個全局變量標識耗時的程序處理的進度。
代碼段簡化如下:
int num=0; int number=16; private void button2_Click(object sender, EventArgs e) { progressBar1.Maximum = number; // Thread myThread = new Thread(DoData); Thread myThread = new Thread(new ParameterizedThreadStart(DoData)); myThread.IsBackground = true; myThread.Start(number); //線程開始 } private void DoData(object number) { Console.WriteLine("准備進行循環調用"); for (int i = 0; i < (int)number; i++) { //這里是一段耗時長的代碼 num = i + 1; } } }
然后加入一個Timer每隔一定的時間訪問num的值。在Timer_tick函數中更新progressBar的value的值。
補充:在C#中的子線程中不能直接更新UI中的控件值。可以通過
1.Invoke或者BeginInvoke的方式調用委托的方法
2.使用BackGroundWorker類取代Thread類進行異步操作
3.通過設置窗體屬性,取消線程安全檢查來“避免跨線程操作異常”。(非線程安全,不建議使用)
4.通過UI線程的SynchronizationContext的Post/Send方法更新。