http://hi.baidu.com/jiang_yy_jiang/blog/item/c96c4826299fce008b82a126.html
先聲明,大部分資料均參考網上,進行了整理。
1.
在VS中添加了BackgroundWorker組件,該組件在多線程編程方面使用起來非常方便,然而在開始時由於沒有搞清楚它的使用機制,走了不少的彎路,現在把我在使用它的過程中的經驗與諸位分享一下。
BackgroundWorker類中主要用到的有這列屬性、方法和事件:
重要屬性:
1、CancellationPending 擷取一個值,指示應用程式是否已請求取消後台操作。通過在DoWork事件中判斷CancellationPending屬性可以認定是否需要取消後台操作(也就是結束線程);
2、IsBusy 擷取一個值,指示 BackgroundWorker 是否正在運行非同步作業。程式中使用IsBusy屬性用來確定後台操作是否正在使用中;
3、WorkerReportsProgress 擷取或設定一個值,該值指示BackgroundWorker能否報告進度更新
4、WorkerSupportsCancellation 擷取或設定一個值,該值指示 BackgroundWorker 是否支援非同步取消。設定WorkerSupportsCancellation為true使得程式可以調用CancelAsync方法提交終止掛起的後台操作的請求;
重要方法:
1、CancelAsync 請求取消掛起的後台操作
2、RunWorkerAsync 開始執行後台操作
3、ReportProgress 引發ProgressChanged事件
重要事件:
1、DoWork 調用 RunWorkerAsync 時發生
2、ProgressChanged 調用 ReportProgress 時發生
3、RunWorkerCompleted 當後台操作已完成、被取消或引發異常時發生
另外還有三個重要的參數是RunWorkerCompletedEventArgs以及DoWorkEventArgs、ProgressChangedEventArgs。
BackgroundWorker的各屬性、方法、事件的調用機制和順序:
從可見在整個生活周期內發生了3次重要的參數傳遞過程:
參數傳遞1:此次的參數傳遞是將RunWorkerAsync(Object)中的Object傳遞到DoWork事件的DoWorkEventArgs.Argument,由於在這裡只有一個參數可以傳遞,所以在實際應用往封裝一個類,將整個執行個體化的類作為RunWorkerAsync的Object傳遞到DoWorkEventArgs.Argument;
參數傳遞2:此次是將程式運行進度傳遞給ProgressChanged事件,實際使用中往往使用給方法和事件更新進度條或者日誌資訊;
參數傳遞3:在DoWork事件結束之前,將後台線程產生的結果資料賦給DoWorkEventArgs.Result一邊在RunWorkerCompleted事件中調用RunWorkerCompletedEventArgs.Result屬性取得後台線程產生的結果。
另外從可以看到DoWork事件是在後台線程中啟動並執行,所以在該事件中不能夠操作使用者介面的內容,如果需要更新使用者介面,可以使用ProgressChanged事件及RunWorkCompleted事件來實現。
明白了BagkgroundWorker的事件調用順序和參數傳遞機制之後在使用該組件用於多線程編程的時候就可以輕鬆許多了。詳細的執行個體可以在我寫的天涯離線瀏覽器中看到。
2.
凡是WinForm的應用程式,如果他執行了一個的非常冗長的處理操作(比如檔案查詢),它在執行時會鎖定使用者介面,雖然主使用中視窗 一直在運行,但使用者無法與程式互動,無法移動表單或改變表單大小,所以使用者感覺很不爽。如何做才能使得這個程式有響應。答案就是在後台線程中執行這個操 作。
在這裡已經有了多種方法來做這個事情:
(一)委託非同步呼叫
將具體耗時的操作作為一個委託,並用BeginInvoke來非同步執行這個委託(Invoke是同步調用),並且可以為這個操作傳入參數並且通過EndInvoke方法獲得返回傳回值。
(二)使用ThreadPool
建立.net FrameWork中內建的WaitCallback委託,然後放到線程池中運行ThreadPool.QueueUserWorkItem( callback ); 根據WaitCallback委託的定義,可以傳入一個object類型的參數。
但是不能精確的控制線程池中的線程。
(三)使用Thread
和ThreadPool相比,使用Thread的開銷會比較大。但是它有它的優勢,使用 Thread 類可以顯式管理線程。只要有可能,就應該使用 ThreadPool 類來建立線程。然而,在一些情況下,您還是需要建立並管理您自己的線程,而不是使用 ThreadPool 類。在.net 2.0 中,提供了一個新的委託 ParameterizedThreadStart 支援啟動一個線程並傳入參數,這是對原來的ThreadStart委託的改進。
說了這麼多還沒有說到今天的主角BackgroundWorker,他也是一個在2.0中新增的類,可以用於啟動後台線程,並在後台計算結束後調用主線程 的方法.可以看出同樣的功能使用委託的非同步呼叫也可以實現,只是使用BackgroundWorker的話會更加的簡便快捷,可以節省開發時間,並把你從 建立自己的委託以及對它們的調用中解救出來。真是這樣的嗎看看下面這個例子。其實我也是從101Samples中看到的例子。
先看看BackgroundWorker中的主要概念。
第一:主要的事件及參數。
DoWork——當執行BackgroundWorker.RunWorkerAsync方法時會觸發該事件,並且傳遞DoWorkEventArgs參數;
ProgressChanged——操作處理中獲得的處理狀態變化,通過BackgroundWorker.ReportProgress(int)方法 觸發該事件,並且傳遞ProgressChangedEventArgs,其中包含了處理的百分比;
RunWorkerCompleted ——非同步作業完成後會觸發該事件,當然如果需要在操作過程中結束可以執行BackgroundWorker.CancelAsync方法要求非同步呼叫中 止,並且在非同步委託操作中檢測BackgroundWorker.CancellationPending屬性如果為true的話,跳出非同步呼叫,同時將 DoWorkEventArgs.Cancel屬性設為true,這樣當退出非同步呼叫的時候,可以讓處理RunWorkerCompleted事件的函數 知道是正常退出還是中途退出。
第二:主要的方法。
BackgroundWorker.RunWorkerAsync——
“起動”非同步呼叫的方法有兩次重載RunWorkerAsync(),RunWorkerAsync(object argument),第二個重載提供了一個參數,可以供非同步呼叫使用。(如果有多個參數要傳遞怎麼辦,使用一個類來傳遞他們吧)。調用該方法後會觸發 DoWork事件,並且為處理DoWork事件的函數DoWorkEventArg事件參數,其中包含了RunWorkerAsync傳遞的參數。在相應 DoWork的處理函數中就可以做具體的複雜操作。
BackgroundWorker.ReportProgress——
有時候需要在一個冗長的操作中向使用者不斷反饋進度,這樣的話就可以調用的ReportProgress(int percent),在調用 ReportProgress 方法時,觸發ProgressChanged事件。提供一個在 0 到 100 之間的整數,它表示後台活動已完成的百分比。你也可能提供任何對象作為第二個參數,允許你 給事件處理常式傳遞狀態資訊。作為傳遞到此過程的 ProgressChangedEventArgs 參數屬性,百分比和你自己的對象(如果提供的話)均要被傳遞到 ProgressChanged
事件處理常式。這些屬性被分別命名為 ProgressPercentage 和 UserState,並且你的事件處理常式可以以任何需要的方式使用它們。(注意:只有在 BackgroundWorker.WorkerReportsProgress屬性被設定為true該方法才可用)。
BackgroundWorker.CancelAsync——
但需要退出非同步呼叫的時候,就調用的這個方法。但是樣還不夠,因為它僅僅是將BackgroudWorker.CancellationPending屬 性設定為true。你需要在具體的非同步呼叫處理的時候,不斷檢查BackgroudWorker.CancellationPending是否為 true,如果是真的話就退出。(注意:只有在BackgroundWorker.WorkerSupportsCancellation屬性被設定為 true該方法才可用)。
貼出一段101Samples裡面的代碼,看一下就明白了:
public partial
class MainForm : Form
{
private System.ComponentModel.BackgroundWorker backgroundCalculator;
public MainForm()
{
InitializeComponent();
backgroundCalculator =new BackgroundWorker();
backgroundCalculator.WorkerReportsProgress =true;
backgroundCalculator.WorkerSupportsCancellation =true;
backgroundCalculator.DoWork +=
new DoWorkEventHandler(backgroundCalculator_DoWork);
backgroundCalculator.ProgressChanged +=
new ProgressChangedEventHandler(backgroundCalculator_ProgressChanged);
backgroundCalculator.RunWorkerCompleted +=
new RunWorkerCompletedEventHandler(backgroundCalculator_RunWorkerCompleted);
updateStatus(String.Empty);
}
privateint getNextPrimeAsync(int start, BackgroundWorker worker, DoWorkEventArgs e)
{
int percentComplete
=0;
start++;
while (!isPrime(start))
{
// Check for cancellation
if (worker.CancellationPending)
{
e.Cancel =true;
break;
}
else
{
start++;
percentComplete++;
worker.ReportProgress(percentComplete %100);
}
}
return start;
}
void backgroundCalculator_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
if (e.Cancelled)
{
updateStatus("Cancelled.");
}
elseif (e.Error
!=null)
{
reportError(e.Error);
}
else
{
reportPrime((int)e.Result);
}
calcProgress.Value =0;
}
void backgroundCalculator_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
updateProgress(e.ProgressPercentage);
}
void backgroundCalculator_DoWork(object sender, DoWorkEventArgs e)
{
int start
= (int) e.Argument;
e.Result = getNextPrimeAsync(start, (BackgroundWorker)sender, e);
}
privatevoid nextPrimeAsyncButton_Click(object sender, EventArgs e)
{
updateStatus("Calculating...");
int start;
Int32.TryParse(textBoxPrime.Text, out start);
if (start
==0)
{
reportError("The number must be a valid integer");
}
else
{
// Kick off the background worker process
backgroundCalculator.RunWorkerAsync(int.Parse(textBoxPrime.Text));
}
}
privatevoid cancelButton_Click(object sender, EventArgs e)
{
if (backgroundCalculator.IsBusy)
{
updateStatus("Cancelling...");
backgroundCalculator.CancelAsync();
}
}
// Update the Status label
privatevoid updateStatus(string status)
{
calcStatus.Text = status;
}
// Indicate progress using progress bar
privatevoid updateProgress(int percentComplete)
{
calcProgress.Value = percentComplete;
}
}
BackgroundWorker建立自己的委託並調用這個表單的 Invoke 方法來運行它,BackgroundWorker 組件以一種優雅的方式來處理這個線程轉換。BackgroundWorker 組件允許你從後台線程中調用它的 ReportProgress 方法,該方法觸發其 ProgressChanged 事件處理常式返回到表單的線程中。你不必使用 delegate/Invoke 方法自己處理這個線程轉換,而是調用 ReportProgress,其餘的事情交給組件來做。