.NET跨線程控件的相關操作
在.NET中,如果我們在非UI線程上訪問窗體上的控件的時候,會產生一個跨線程調用的異常,那么如何處理這種情況呢?在上一章中,我介紹了使用Control.Invoke方法,如果你不習慣使用委托,那么.Net還為我們提供了一個組件BackgroundWorker,你可以使用這個組件,以事件的方式去處理這種跨線程的控件訪問。下面我就來詳細的介紹一下這個組件的用法。
我們先來看一下BackgroundWorker提供了哪些常用的成員,
事件
◆DoWork:我們在這個事件中,執(zhí)行需要異步處理的工作。
◆ProgressChanged:我們在這個事件中,接收并處理異步處理過程中的信息。
◆RunWorkerCompleted:我們在這個事件中,執(zhí)行異步處理結束的工作。
方法
◆RunWorkerAsync()和RunWorkerAsync(object argument):這兩個方法觸發(fā)DoWork事件,開始異步操作。
◆ReportProgress(int percentProgress)和ReportProgress(int percentProgress, object userState):這兩個方法觸發(fā)ProgressChanged事件。
◆CancelAsync:結束后臺的異步操作。
屬性
◆bool CancellationPending:指示當前的后臺的異步操作是否正在被取消,執(zhí)行CancelAsync方法會導致這個屬性為true。
◆bool IsBusy:指示當前的后臺異步操作是否正在進行,進行中為true。
◆bool WorkerReportsProgress:獲取或設置當前的BackgroundWorker是否可以執(zhí)行ProgressChanged方法。
◆bool WorkerSupportsCancellation:獲取或設置當前的BackgroundWorker是否可以執(zhí)行CancelAsync方法。
OK,有了上面這些成員,我們來看一下BackgroundWorker是如何工作的。
Step 1. 當然是定義一個BackgroundWorker的實例,你可以從工具箱中拖拽一個BackgroundWorker控件到窗體上或者在代碼中直接聲明;
Step 2. 生成DoWork事件并在DoWork事件的中添加需要異步執(zhí)行的代碼。在異步執(zhí)行的代碼中,如果需要處理界面中的控件,請調用ReportProgress方法,而不要直接處理(例如給控件賦值),因為DoWork事件跟正常的界面的事件不同,這個事件在非UI線程上執(zhí)行,所以才能異步執(zhí)行。
Step 3. 生成ProgressChanged事件并添加控件處理的代碼,因為這個事件是在UI線程上執(zhí)行的,所以可以給界面中的控件進行賦值等操作。
Step 4. 如果需要,請生成RunWorkerCompleted事件,在此處理異步執(zhí)行結束的業(yè)務邏輯。當然,此事件也是在UI線程上執(zhí)行的,可以給界面中的控件進行賦值等操作。
Step 5. 在需要執(zhí)行異步操作的地方調用RunWorkerAsync方法,開始執(zhí)行異步調用。
下面是具體的代碼:
- public Form1()
- {
- InitializeComponent();
- bWorker.DoWork += new DoWorkEventHandler(bWorker_DoWork);
- bWorker.RunWorkerCompleted += new RunWorkerCompletedEventHandler(bWorker_RunWorkerCompleted);
- bWorker.ProgressChanged += new ProgressChangedEventHandler(bWorker_ProgressChanged);
- this.Text = "UI thread id is:" + Thread.CurrentThread.ManagedThreadId.ToString();
- }
- BackgroundWorker bWorker = new BackgroundWorker();
- void bWorker_DoWork(object sender, DoWorkEventArgs e)
- {
- int tick = (int)e.Argument;
- Thread thr = Thread.CurrentThread;
- for (int i = 0; i < 30; i++)
- {
- if (bWorker.CancellationPending)
- {
- e.Cancel = true;
- //break;
- }
- else
- {
- Thread.Sleep(TimeSpan.FromSeconds(tick));
- bWorker.ReportProgress(i, DateTime.Now.ToString() + "\\TID:" + thr.ManagedThreadId.ToString());
- }
- }
- }
- void bWorker_ProgressChanged(object sender, ProgressChangedEventArgs e)
- {
- progressBar1.Value = e.ProgressPercentage;
- label1.Text = e.UserState.ToString();
- }
- void bWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
- {
- label1.Text = DateTime.Now.ToString();
- progressBar1.Value = progressBar1.Maximum;
- if (e.Cancelled)
- label1.Text = "User cancelled.";
- }
- private void btnInvoke_Click(object sender, EventArgs e)
- {
- bWorker.WorkerReportsProgress = true;
- bWorker.WorkerSupportsCancellation = true;
- if (!bWorker.IsBusy)
- bWorker.RunWorkerAsync(1);
- }
- private void btnCancel_Click(object sender, EventArgs e)
- {
- if (bWorker.WorkerSupportsCancellation)
- bWorker.CancelAsync();
- }
上面的代碼請注意幾個地方:
1. 第50行,開始調用RunWorkerAsync方法前,請先判斷IsBusy屬性是否是false,因為如果為true,則說明上一次的調用還沒有結束,再次調用會引發(fā)異常。
2. 第56行,調用CancelAsync方法前,請先設置WorkerSupportsCancellation屬性為true,否則會引發(fā)異常。
3. 第26行,調用ReportProgress方法前,請先設置WorkerReportsProgress屬性為true,否則會引發(fā)異常。
4. RunWorkerAsync方法傳遞的參數(shù)是object類型,這個參數(shù)的值可以在DoWork事件的參數(shù)e中的屬性Argument獲得。
5. ReportProgress方法傳遞的參數(shù)可以在事件ProgressChanged中的參數(shù)e中獲得。
6. 調用CancelAsync方法只是向后臺的異步線程發(fā)出結束申請,具體什么時候結束,由線程自動管理。
7. 在RunWorkerCompleted事件中,如果想知道后臺任務是正常執(zhí)行完畢還是被調用CancelAsync方法強制中斷,請參考事件的參數(shù)e的Cancelled屬性。(奇怪的是這個屬性不會在你調用CancelAsync方法后自動設置為true,你需要象代碼中的20行那樣進行設置。)
8. 請注意第7行和第26行的代碼,這兩段代碼中的線程的ID,說明了DoWork事件和UI是在兩個不同的線程上執(zhí)行。
實際上BackgroundWorker并非直接用來解決跨線程的控件調用的問題,只是它提供了一種工作機制,可以讓你的程序利用它來執(zhí)行異步調用,并且在異步調用的過程中進行控件的操作。
好了,關于如何對界面中的控件進行跨線程的調用就介紹這么多吧,希望對大家有所幫助。
原文標題: 在.Net中進行跨線程的控件操作(下篇:BackgroundWorker)
鏈接:http://www.cnblogs.com/happinessCodes/archive/2010/07/22/1783199.html
【編輯推薦】