淺談C#多線程與UI響應(yīng)
一.C#多線程概述
1.后臺循環(huán)任務(wù),少量UI更新:例如批量上傳文件,并提供進度。這種情況使用BackgroundWorker組件是非常好的選擇。
2.耗時的后臺任務(wù):這里的耗時任務(wù)是指一個時間較長的任務(wù),并且不能精確獲取進度,如:調(diào)用一個遠程WebService接口。這種情況可以開兩個線程,一個工作,一個更新UI(不能提供進度,只能顯示動畫表示系統(tǒng)在運行中)。
3.耗時的UI任務(wù):當(dāng)工作壓力集中在UI響應(yīng)上時,可以在工作者線程中增加延時,從而讓UI線程獲得響應(yīng)時間。整個工作的總體時間會增加,但用戶響應(yīng)效果會好很多。
二.后臺的循環(huán)任務(wù),少量UI更新
這種情況使用BackgroundWorker組件是最好的選擇。(詳見附一)
三.后臺耗時任務(wù)
在后臺執(zhí)行一個不可分解的耗時任務(wù),需要進行界面更新,以便讓客戶看上去程序有所響應(yīng)。這種情況下,UI線程一般也不知道工作線程何時結(jié)束,所以一般執(zhí)行循環(huán)任務(wù),當(dāng)工作線程結(jié)束后,關(guān)閉UI線程就可以了。
- Threaduithread=null;
- privatevoidbtnStart_Click(objectsender,EventArgse)
- {
- uithread=newThread(newThreadStart(this.UpdateProgressThread));
- uithread.Start();
- Threadworkthread=newThread(newThreadStart(this.DoSomething));
- workthread.Start();
- }
- privatevoidDoSomething()
- {
- Thread.Sleep(5000);
- uithread.Abort();
- MessageBox.Show("workend");
- }
- privatevoidUpdateProgressThread()
- {
- for(inti=0;i<10000;i++)
- {
- Thread.Sleep(100);
- this.Invoke(newAction(this.UpdateProgress),i);
- }
- }
- privatevoidUpdateProgress(intv)
- {
- this.progressBar1.Value=v;
- }
這里只要注意一點:線程調(diào)用的方法都不能訪問用戶控件,必須通過委托調(diào)用Form的方法來實現(xiàn)界面更新。
四.耗時的UI任務(wù)
當(dāng)整個工作壓力集中在UI響應(yīng)上時,可以在工作者線程中增加延時,從而讓UI線程獲得響應(yīng)時間。整個工作的總體時間會增加,但用戶響應(yīng)效果會好很多。
- privatevoidFormInitForm_Load(objectsender,EventArgse)
- {
- this.listView1.Items.Clear();
- Threadworkthread=newThread(newThreadStart(this.DoSomething));
- workthread.Start();
- }
- privatevoidDoSomething()
- {
- for(inti=0;i<30;i++)
- {
- this.Invoke(newAction(this.LoadPicture),i);
- Thread.Sleep(100);
- }
- }
- privatevoidLoadPicture(inti)
- {
- stringstringtext=string.Format("Item{0}",i);
- ListViewItemlvi=newListViewItem(text,0);
- this.listView1.Items.Add(lvi);
- Thread.Sleep(200);//模擬耗時UI任務(wù),非循環(huán),不可分解
- }
五.補充
1.Invoke和BeginInvoke
在多線程編程中,我們經(jīng)常要在工作線程中去更新界面顯示,而在多線程中直接調(diào)用界面控件的方法是錯誤的做法,正確的做法是將工作線程中涉及更新界面的代碼封裝為一個方法,通過Invoke或者BeginInvoke去調(diào)用,兩者的區(qū)別就是一個導(dǎo)致工作線程等待,而另外一個則不會。
而所謂的“一面響應(yīng)操作,一面添加節(jié)點”永遠只能是相對的,使UI線程的負擔(dān)不至于太大而以,因為界面的正確更新始終要通過UI線程去做,我們要做的事情是在工作線程中包攬大部分的運算,而將對純粹的界面更新放到UI線程中去做,這樣也就達到了減輕UI線程負擔(dān)的目的了。
2.Application.DoEvent
在耗時的循環(huán)的UI更新的方法中,插入Application.DoEvent,會使界面獲得響應(yīng),Application.DoEvent會調(diào)用消息處理程序。
- privatevoidbutton2_Click(objectsender,EventArgse)
- {
- for(inti=0;i<30;i++)
- {
- stringstringtext=string.Format("Item{0}",i);
- ListViewItemlvi=newListViewItem(text,0);
- this.listView1.Items.Add(lvi);
- Thread.Sleep(200);
- for(intj=0;j<10;j++)
- {
- Thread.Sleep(10);
- Application.DoEvents();
- }
- }
- }
- 3.Lock
- lock(object)
- {
- }
- 等價與
- try
- {
- Monitor.Enter(object);
- }
- finally
- {
- Monitor.Exit(object)
- }
附一:
BackgroundWorker組件使用說明
一.概述
BackgroundWorker是·NET2.0提供的一個多線程組件,在應(yīng)用程序中使用,可以非常簡單方便地實現(xiàn)UI控件通信,并自動處理多線程沖突問題。
二.基本屬性
1.WorkerReportsProgress,bool:是否允許報告進度;
2.WorkerSupportsCancellation,bool:是否允許取消線程。
3.CancellationPending,bool,get:讀取用戶是否取消該線程。
三.基本事件
1.DoWork:工作者線程
2.RunWorkerCompleted:線程進度報告
3.ProgressChanged:線程結(jié)束報告
四.基本方法
1.RunWorkerAsync():啟動工作者線程;
2.CancelAsync():取消工作者線程;
3.ReportProgress(int);報告進度
五.代碼
- //啟動
- privatevoidbtnStart_Click(objectsender,EventArgse)
- {
- this.btnStart.Enabled=false;
- this.btnStop.Enabled=true;
- this.backgroundWorker.RunWorkerAsync();
- }
- //通知線程停止
- privatevoidbtnStop_Click(objectsender,EventArgse)
- {
- this.backgroundWorker.CancelAsync();
- }
- //工作者線程
- privatevoidbackgroundWorker_DoWork(objectsender,DoWorkEventArgse)
- {
- for(inti=0;i<150;i++)
- {
- if(backgroundWorker.CancellationPending)//查看用戶是否取消該線程
- {
- break;
- }
- System.Threading.Thread.Sleep(50);//干點實際的事
- backgroundWorker.ReportProgress(i);//報告進度
- }
- }
- //線程進度報告
- privatevoidbackgroundWorker_ProgressChanged(objectsender,ProgressChangedEventArgse)
- {
- this.progressBar1.Value=e.ProgressPercentage*100/150;
- }
- //線程結(jié)束報告
- privatevoidbackgroundWorker_RunWorkerCompleted(objectsender,RunWorkerCompletedEventArgse)
- {
- this.btnStart.Enabled=true;
- this.btnStop.Enabled=false;
- }