2009年10月5日 星期一

在WPF中使用背景工作(BackgroundWorker)

在程式中有時候會需要執行一些比較花時間的工作
比如連上網路下載或上傳東西
但在和網路溝通及傳輸的時候,畫面上的工作就會暫停
這時候會想要提示使用者程式還在工作,比如說在msn登入的時候撥放動畫

這時候就要用到背景工作了,實際上這就是一種多執行緒處理
只是.net提供了方便的包裝,讓主執行緒在背景工作執行中得知進度
BackgroundWorker主要有三個事件處理
事件說明
DoWork在背景執行的工作,為另外一個執行緒,不可以變更介面畫面(介面變更在後面兩個事件中處理)
RunWorkerCompleted背景工作完成後的處理,因為背景工作是非同步處理,所以在完成後會觸發這事件來啟動主執行緒接下來的動作,如登入完成後執行切換畫面
ProgressChanged如果動作費時太長,有時候會希望將進度反應在介面上,在這裡改變介面畫面

我們來做一個範例(參考HOW TO:使用幕後背景工作)
視窗中只有一個按鈕(Button)

<Window ...>
<Button Name="b" Click="Button_Click" />
</Window>

在按鈕按下的時候開始設定並執行背景工作

private void Button_Click(
object sender, RoutedEventArgs e)
{
/// new and allow cancel and report progress
BackgroundWorker bw = new BackgroundWorker();
bw.WorkerSupportsCancellation = true;
bw.WorkerReportsProgress = true;

/// add the event handler for each progress
bw.DoWork += new DoWorkEventHandler(DoWork);
bw.ProgressChanged +=
new ProgressChangedEventHandler(DuringWork);
bw.RunWorkerCompleted +=
new RunWorkerCompletedEventHandler(AfterWork);

/// start the background work
bw.RunWorkerAsync();
}

事件處理定義如下

void DoWork(object sender, DoWorkEventArgs e)
{
BackgroundWorker w = sender as BackgroundWorker;

for (int i = 1; i <= 10; i++)
{
/// check if the work is cancelled
if (w.CancellationPending == true)
{
e.Cancel = true;
break;
}

/// perform a time consuming operation
Thread.Sleep(500);

/// report progress in percentage
w.ReportProgress(i * 10);
}
}

void DuringWork(
object sender, ProgressChangedEventArgs e)
{
/// reflect the change of progress in UI
b.Content =
e.ProgressPercentage.ToString() + "%";
}

void AfterWork(
object sender, RunWorkerCompletedEventArgs e)
{
/// reflect the result after background work
if (e.Cancelled == true)
{
b.Content = "Canceled!";
}
else if (!(e.Error == null))
{
b.Content = ("Error: " + e.Error.Message);
}
else
{
b.Content = "Done!";
}
}

這個程式執行後,按下按鈕的時候
就會開始執行DoWork裡面的動作(每半秒更新一下進度)
並在每次更新進度的時候更新Button上面顯示的文字

另外,有時候背景程式需要使用到介面輸入的資訊
(比如登入畫面,需要將帳號密碼傳上網路)
但是DoWork裡面不能使用介面的物件
這時候可以透過argument(object形態,可以傳任意型別)傳入
所以啟動背景工作的呼叫改寫成

private void Button_Click(
object sender, RoutedEventArgs e)
{
/// new and allow cancel and report progress
BackgroundWorker bw = new BackgroundWorker();
bw.WorkerSupportsCancellation = true;
bw.WorkerReportsProgress = true;

/// add the event handler for each progress
bw.DoWork += new DoWorkEventHandler(DoWork);
bw.ProgressChanged +=
new ProgressChangedEventHandler(DuringWork);
bw.RunWorkerCompleted +=
new RunWorkerCompletedEventHandler(AfterWork);

/// start the background work with argument
bw.RunWorkerAsync(b.Content);
}

而在DoWork裡面則是用e.Argument將傳入的參數讀出
如果有超過一個以上的參數,也可以利用ArrayList先打包起來再一起傳

--
參考資料
HOW TO:使用幕後背景工作
BackgroundWorker 類別

沒有留言:

張貼留言