1. 程式人生 > >C# BackgroundWorker實現WinForm非同步操作的例子

C# BackgroundWorker實現WinForm非同步操作的例子

最近的一個Smart Client專案中,為了演示非同步操作的實現,寫了一個基於BackgorundWorker的例子。由於這個理基本上實現了BackgorundWorker的大部分功能:非同步操作的啟動、操作結束後的回撥、非同步操作的撤銷和進度報告等等。儘管沒有太多的技術含量,姑且放上來與大家分享。

1、場景描述

下面是程式執行時的截圖。本程式模擬這樣的一個場景:有兩組相互獨立的資料需要逐條獲取和顯示,左邊和右邊兩個groupbox分別代表基於這兩組資料的操作,由於他們完全獨立,因此可以並行執行。當點選Start按鈕,以非同步的方式從儲存介質中逐條獲取資料,並將獲取的資料追加到對應的ListBox中,ProgressBar真實反映以獲取的資料條數和總記錄條數的百分比,同時,當前獲取的條數也會在下方的Label上隨著操作的繼續而動態變化。此外通過點選Stop按鈕,可以中止掉當前的操作。當操作被中止後,ProgressBar和Label反映中止的那一刻的狀態。

background._01_01

2、程式碼實現

由於介面上左右兩邊是兩個互不干擾、相互獨立的操作,所以分別建立了兩個BackgroundWorker元件來負責(如下圖:backgroundWorkerLeft和backgroundWorkerRight)。

background._01_2

將兩個BackgroundWorker的WorkerReportsProgress和WorkerSupportsCancellation設為true。

background._01_03

我們假設獲取的記錄數固定,我們為此定義一個常量:

private static int MaxRecords = 100;

下面是左邊Start按鈕的Click event handler:

private void buttonStartLeft_Click(object sender, EventArgs e)
{
    if (this.backgroundWorkerLeft.IsBusy)
    {
return;
    }
    this.listBoxLeft.Items.Clear();
    this.backgroundWorkerLeft.RunWorkerAsync(MaxRecords);
    this.buttonStartLeft.Enabled = false;
    this.buttonCacnelLeft.Enabled = true;
}

當Start按鈕被點選後,RunWorkerAsync方法被掉呼叫,我們定義的常量(MaxRecords )當作引數被摻入。隨後,將會觸發其DoWork事件,Dowork event handler處理程式碼如下:

private void backgroundWorkerLeft_DoWork(object sender, DoWorkEventArgs e)
{
    try
    {
e.Result = this.RetrieveData(this.backgroundWorkerLeft, e);
    }
    catch (Exception ex)
    {
       MessageBox.Show(ex.Message);
       throw;
    }
}

呼叫RetrieveData方法逐條獲取資料。注意該方法的兩個引數:BackgroundWorker和DoWorkEventArgs 物件,返回值是返回資料的數量。由於在buttonStartLeft_Click中,我們將常量MaxRecords 作為引數傳入了BackgroundWorker的RunWorkerAsync方法, 此時的e.Argument = MaxRecords。之所以要將這兩個引數傳入RetrieveData()方法,是因為該方法是為兩個BackgroundWorker服務的,需要通過引數來區別當前是哪個BackgroundWorker。我們再來看看RetrieveData方法的定義:

private int RetrieveData(BackgroundWorker worker, DoWorkEventArgs e)
{
    int maxRecords = (int)e.Argument;
    int percent = 0;
    for (int i = 1; i <= maxRecords; i++)
    {
        if (worker.CancellationPending)
        {
            return i;
        }

        percent = (int)(((double)i / (double)maxRecords) * 100);
        worker.ReportProgress(percent, new KeyValuePair<int,string>(i,Guid.NewGuid().ToString()));
        Thread.Sleep(100);
    }

    return maxRecords;
}

通過e.Argument,獲得最大資料獲取量之後,進行一個for迴圈,在每次迭代中,如何worker.CancellationPending==true,代表非同步操作被顯示取消,則直接返回;否則,呼叫BackgroundWorker的ReportProgress方法。ReportProgress具有兩個過載:

  • public void ReportProgress(int percentProgress);
  • public void ReportProgress(int percentProgress, object userState);

percentProgress代表當前進度,從0-100。userState便於傳入一些額外的引數。在介面上,由於資料的當前數量需要實時地顯示,而記錄也是現取現加(取出一條就在ListBox上追加)。所以制定一個KeyValuePair<int,string>物件作為第二個引數。其中Key為當前記錄數,Value是一個Guid,代表取出的資料。

ReportProgress的呼叫將會導致ProgressChanged事件被觸發。ProgressChanged event handler用於顯示當前進度、當前記錄數量和顯示獲取的紀錄:

private void backgroundWorkerLeft_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
    KeyValuePair<int,string> record = (KeyValuePair<int,string>) e.UserState ;
    this.labelResultLeft.Text = string.Format("There are {0} records retrieved!", record.Key);
    this.progressBarLeft.Value = e.ProgressPercentage;
    this.listBoxLeft.Items.Add(record.Value);
}

注:這些操作需要操作UI上的控制元件,只能在Main Thread中進行。如何在RetrieveData方法進行的話,由於該方式是一個非同步方法,是會丟擲異常的。

由於操作的時間可能無法預知,在長時間不能完全獲取資料的情況下,使用者可以需要手工結束掉當前的操作。這個操作實現在Stop按鈕的Click事件中:

private void buttonCacnelLeft_Click(object sender, EventArgs e)
{
    this.backgroundWorkerLeft.CancelAsync();
}

如何操作正常地結束,BackgroundWorker的RunWorkerCompleted會被觸發,下面是RunWorkerCompleted

event handler的定義:

private void backgroundWorkerLeft_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
    try
    {
        this.labelResultLeft.Text = string.Format("Total records: {0}", e.Result);
        this.buttonStartLeft.Enabled = true;
        this.buttonCacnelLeft.Enabled = false;
    }
    catch (TargetInvocationException ex)
    {
        MessageBox.Show(ex.InnerException.GetType().ToString());
    }
}

上面介紹的是介面左邊功能的實現,右邊部分的實現完全一致。幹興趣的朋友可以參考Source Code.