2017年8月25日 星期五

C#: BackgroundWorker 非同步 /背景多執行緒 舉例




[Ref]

在專案開發時,多多少少一定會處理到大量資料或檔案存取的時候,然而每當這些工作一開始的時候,
介面總是會因為工作尚未完成而遭到鎖死或失去回應,對使用者來說十分不友善,
而且執行情況也無法被觀察,而且結果像是桃太郎一樣突然出現,又無法捉摸。
這種情況,小可在程式開發的時候也時常遇到,有沒有什麼方法可以讓程式正常回應又保持作業呢?
我們可以另外開一條執行緒來執行這些特別的工作,而最簡單又快速的方式就是使用 BackgroundWorker 了!

什麼是 BackgroundWorker 呢?簡單來說和另開新的 Thread 情況相差不遠,且可以將指派的工作分配到背景,
讓前景的介面不會因為資源被吃光而停止回應,但用途還不只這麼單調,更多用法還等待著呢!
只是這種用途是最時常被用上,也最直覺就是了。
聽起來很有趣,那這個東西該怎麼用?在使用之前小可要做一些解說。

舉著情境例子,今天小可是老師,正在某間教室裡面做監考,不過面前有一大疊考卷等著批改,
而這批考卷必須在一堂課內解決,但在批改的時候又得要監考,
監考相較於批改考卷來得嚴謹,但批改考卷又是重要的工作,而且非常分神又費時,這該怎麼做才好?
雖然有點不太貼切,但大家在學生期間,應該都有幫老師改考卷的經驗吧。
小可馬上撥打手機,指派一個值得信任的學生來教室,代替小可批改考卷,而自己就可以專注在考場監考了。

好像是個滿不錯的解決方法,但這個和這篇文章的主題有什麼關係?
上面的情境,改考卷是個粗重的工作,小可把它指派給他人作業,自己則全神在考場抓作弊。
在主題中,就好像主程式把費時的工作,轉交給 BackgroundWorker 執行,主程式則繼續監視介面的一舉一動。

好像有點關聯了,不過這還不夠。
小可在監考,應該要可以隨時打手機問考卷改到哪裡吧?如果全部改完了就叫他回撥通知,之後把考卷抱來給小可。
同理,主程式可以隨時得知工作進行的進度,如果工作完成了,就把結果回傳給主程式。

嗯!這樣就足夠了!雖然廢話有點多,不過這是希望能幫助大家了解這個 BackgroundWorker 到底在幹嘛。

上面說了這麼多,是要為 BackgroundWorker 的用途做基本解說,因為小可也不喜歡用硬梆梆的語言來說明。
BackgroundWorker 的主要用途就這樣,這就開始要做連接囉!

我們該怎麼把工作交給 BackgroundWorker 呢?很容易的!
首先,我們只要宣告一個 BackgroundWorker 物件,或是在介面設計建立,做一些設定就可以開始交派作業了。
BackgroundWorker bgwWorker = new BackgroundWorker();
bgwWorker.DoWork += new DoWorkEventHandler(bgwWorker_DoWork);
bgwWorker.RunWorkerCompleted += newRunWorkerCompletedEventHandler(bgwWorker_RunWorkerCompleted);
bgwWorker.ProgressChanged += newProgressChangedEventHandler(bgwWorker_ProgressChanged);
bgwWorker.WorkerReportsProgress = true;
bgwWorker.WorkerSupportsCancellation = true;
宣告沒問題,那麼那些設定值是什麼意思?
WorkerReportsProgress:希望能隨時知道考卷改到哪嗎?
WorkerSupportsCancellation:想要隨時叫他停下批改作業嗎?
就這麼簡單,一個是要 BackgroundWorker 回報工作進度,一個是可以隨時停止 BackgroundWorker 的作業。
如果是用介面拖拉的方式建立的話,只要在右下邊的屬性欄更改設定就行了。

好吧,我們懂了,也宣告了,那麼我麼就開始交派作業吧!
bgwWorker.RunWorkerAsync();
RunWorkerAsync() 這個方法,表示要 BackgroundWorker 新開一條執行緒,並且觸發 DoWork 事件,
所以我們只要把工作內容寫在DoWork()方法裡面,BackgroundWorker 就會開始執行我們的工作了。
private void bgwWorker_DoWork(object sender, DoWorkEventArgs e)
{
    還沒改的考卷  = (考卷)e.Argument;
    改考卷
}
當然我們可以在 RunWorkerAsync 裡面加上我們的參數,而參數使用 e.Argument 取出,記得要轉型。

考卷改到哪了?
那就加入 ReportProgress() 來問問吧!
private void bgwWorker_DoWork(object sender, DoWorkEventArgs e)
{
    改考卷
    bgwWorker.ReportProgress(進度到哪了);
    繼續改考卷
    e.Result = 改完的考卷
}

private void bgwWorker_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
    progressbar1.Value = e.ProgressPercentage;
}
使用了ReportProgress() 方法後會觸發ProgressChanged事件,所以只要在這個方法裡表現進度就行了。
值得一提的,參數範圍是整數 0~100,表示進度的百分比,而怎麼建立這個值就端看工作比值了,
而這個值最適合用進度條來顯示了,程式碼中也直接使用了。
記得這個進度條要在介面設計裡面先放好。
工作完成的結果可以指派在 e.Result 裡。

考卷改完了!記得會自動抱回來吧!
當工作完成的時候,會自動觸發 RunWorkerCompleted 事件,所以我們只要在這個方法裡面做收尾就可以了。
private void bgwWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
    教室.講桌 = (考卷)e.Result;
}
而工作完成的結果放在 e.Result 裡面,記得要轉型才能使用哦。

臨時反悔,不想要幫忙改了?那就一通電話休了他吧!
private void btnStop_Click(object sender, EventArgs e)
{
    bgwWorker.CancelAsync();
}
通常我們會設定一個按鈕,按下去就會停止工作。而執行 CancelAsync() 方法就會通知有停止的要求。
而在工作中的學生就要隨時注意手機有沒有響了,可以透過 CancellationPending 這個屬性來得知有沒有要求。
private void bgwWorker_DoWork(object sender, DoWorkEventArgs e)
{
    改考卷
    bgwWorker.ReportProgress(進度到哪了);
    if (bgwWorker.CancellationPending)
    {
        生氣丟下考卷
        return;
    }
    繼續改考卷
    e.Result = 改完的考卷
}
不過,如果工作停止了,他可是會生氣的,他可以把考卷堆就此丟下不管,或是他會乖乖把剩下的的交回教室,
看工作是怎麼交代的了哦。

以上就是這個血汗學生 BackgroundWorker 的情境解說,雖然介紹方式很牽強,
不過還是希望能幫助大家對這個好用的東西能有初步認識,大家可以去找更詳細的介紹文章,
說不定能有達到啟蒙的效果,或是當個笑話看看就好囉。其實是寫給未來忘記用法的自己。
另外上面的程式碼都是虛擬碼,直接複製是不能用得哦!

沒有留言:

張貼留言