C#之BackgroundWorker從簡單入門到深刻精通的用法總結

需求分析數據庫

常常用到的耗時操做,例如:編程

一、文件下載和上載(包括點對點應用程序傳輸文件,從網絡下載文件、圖像等)
二、數據庫事務(從數據庫讀到大量的數據到WinForm界面中的DataGridview裏呈現)
三、複雜的本地計算
四、本地磁盤文件訪問(讀寫文件,磁盤文件列表)
……網絡

這些操做在長時間運行時會致使用戶界面 (UI) 處於中止響應狀態,用戶在這操做期間沒法進行其餘的操做,形成很是差的用戶體驗,爲了避免使UI層處於中止響應狀態,則可使用 BackgroundWorker 類方便地解決這類問題。這個後臺的線程處理,能夠很好的實現常規操做的同時,還能夠及時通知UI當前處理信息的進度等。多線程


MSDN的介紹異步

BackgroundWorker是.NET Framework 裏用來執行多線程任務的控件,它容許開發人員在一個單獨的線程上執行一些操做。耗時的操做(以下載和數據庫事務)在長時間運行時可能會致使用戶界面 (UI) 彷佛處於中止響應狀態。 若是您須要能進行響應的用戶界面,並且面臨與這類操做相關的長時間延遲,則可使用 BackgroundWorker 類方便地解決問題。工具

若要在後臺執行耗時的操做,請建立一個 BackgroundWorker,偵聽那些報告操做進度並在操做完成時發出信號的事件。 能夠經過編程方式建立 BackgroundWorker,也能夠將它從「工具箱」的「組件」選項卡中拖到窗體上。 若是在 Windows 窗體設計器中建立 BackgroundWorker,則它會出如今組件欄中,並且它的屬性會顯示在「屬性」窗口中。this

若要爲後臺操做作好準備,請添加 DoWork 事件的事件處理程序。 在此事件處理程序中調用耗時的操做。 若要開始此操做,請調用 RunWorkerAsync。 若要收到進度更新的通知,請處理 ProgressChanged 事件。 若要在操做完成時收到通知,請處理 RunWorkerCompleted 事件。spa

有2點須要注意的:線程

一、因爲DoWork事件內部的代碼運行在非UI線程之上,確保在 DoWork 事件處理程序中不操做任何用戶界面對象。 而應該經過 ProgressChanged 和 RunWorkerCompleted 事件與用戶界面進行通訊。
二、BackgroundWorker 事件不跨 AppDomain 邊界進行封送處理。 請不要使用 BackgroundWorker 組件在多個 AppDomain 中執行多線程操做。設計

 

最簡單示例

準備材料:一個耗時的操做

 代碼以下,這個就很少解釋了:

int iSum = 0; 
private void button1_Click(object sender, EventArgs e)
 {   
  for (int i = 0; i <= 100; i++)
   { 
     iSum+=i; 
     System.Threading.Thread.Sleep(300);   
   }
}

 運行一下,拖動程序界面看看,直接卡死了,假死,一下子,運算完了,就又能夠拖動了。如今用BackgroundWorker來解決這個問題。

 爲此,咱們新建一個WindowsForm命名爲bgwA,拖入一個Label命名爲lblPercent,一個ProgressBar命名爲pgbPercent,一個Button命名爲btnStart。

 

而後,代碼:

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace bgwA
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        private void BtnStart_Click(object sender, EventArgs e)
        {
            BackgroundWorker bgwA = new BackgroundWorker();
            bgwA.WorkerReportsProgress = true;
            bgwA.DoWork += bgwA_DoWork;
            bgwA.ProgressChanged += bgwA_ProgressChanged;
            bgwA.RunWorkerCompleted += bgwA_Completed;
            bgwA.RunWorkerAsync();
        }

        private void bgwA_DoWork(object sender, DoWorkEventArgs e)
        {
            var bgworker = sender as BackgroundWorker;
            for (int i = 0; i <= 100; i++)
            {
                bgworker.ReportProgress(i);
                System.Threading.Thread.Sleep(200);
            }
        }

        private void bgwA_ProgressChanged(object sender, ProgressChangedEventArgs e)
        {
            this.pgbPercent.Value = e.ProgressPercentage;
            this.lblPercent.Text = @"已完成:" + e.ProgressPercentage.ToString() + @"%";
        }
        private void bgwA_Completed(object sender, RunWorkerCompletedEventArgs e)
        {
            this.lblPercent.Text = "後臺操做結束(多是程序100%完成,也多是用戶取消或程序異常致使結束)。";
        }
    }
}

  如今運行一下,點擊btnStart,進度條跑起來了,再拖動一下程序界面,這下不會沒有響應了,不卡,不假死了吧。good.

 

進階一:

 重複上面說到的注意點:因爲DoWork事件內部的代碼運行在非UI線程之上,確保在 DoWork 事件處理程序中不操做任何用戶界面對象。 而應該經過 ProgressChanged 和 RunWorkerCompleted 事件與用戶界面進行通訊。

 

如何在程序運行途中取消正在進行的運算?

要實如今這個功能,咱們首先在程序界面上添加一個能夠隨時停止後臺進程的按鈕BtnCancel,容許用戶在執行過程當中取消當前的操做:

與WorkerReportsProgress屬性同樣,若是要支持取消操做咱們須要設置 WorkerSupportsCancellation屬性爲 true,還要在DoWork方法中進行支持。

接下來對上面的程序進行擴展一下,咱們再新建一個WindowsForm命名爲bgwB,此次,咱們再也不經過代碼來寫BackgroundWorker實例,咱們經過從VS的工具箱中直接拉一個BackgroundWorker出來,而後從屬性窗口中命名爲bgWorker,而後再拖入一個Label命名爲lblPercent,一個ProgressBar命名爲pgbPercent,一個Button命名爲btnStart。

 

 

 DoWork,ProgressChanged,RunWorkerCompleted三個事件都是經過在bgWorker1屬性窗口中雙擊出來的,下面看代碼:

 

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace bgwB
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        private void Form1_Load(object sender, EventArgs e)
        {
            bgWorker1.WorkerReportsProgress = true;
            bgWorker1.WorkerSupportsCancellation = true;
        }

        private void BgWorker1_DoWork(object sender, DoWorkEventArgs e)
        {
            for (int i = 0; i <= 100; i++)
            {
                if (bgWorker1.CancellationPending == true)
                {
                    e.Cancel = true;
                    break;
                }
                else
                {
                    bgWorker1.ReportProgress(i);
                    System.Threading.Thread.Sleep(100);
                }
            }
        }

        private void BgWorker1_ProgressChanged(object sender, ProgressChangedEventArgs e)
        {
            this.pgbPercent.Value = e.ProgressPercentage;
            this.lblPercent.Text = @"已完成:" + e.ProgressPercentage.ToString() + @"%";
        }

        private void BgWorker1_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
        {
            this.lblPercent.Text = "後臺操做結束(多是程序100%完成,也多是用戶取消或程序異常致使結束)。";
        }

        private void BtnStart_Click(object sender, EventArgs e)
        {
            bgWorker1.RunWorkerAsync();
        }

        private void BtnCancel_Click(object sender, EventArgs e)
        {
            bgWorker1.CancelAsync();
        }
    }
}

  如今,咱們能夠按BtnStart啓動後臺任務,也能夠隨時按BtnCancel隨時停止後臺任務。

 

 

發現一個問題沒有?在進度條還沒跑完100%時,再次按btnStart會怎麼樣,先後兩個例子是不同,第一個例子中,再次按btnStart,會感受有兩個重疊的進度條在跑,而第二個例子中,則直接拋出了異常:

爲何會這樣?第一個例子中,每次按btnStart,都是BackgroundWorker bgwA = new BackgroundWorker();而第二個例子中每次按btnStart,仍是那個bgWorker1,只是再次RunWorkerAsync(),解決的方法是先判斷一下後臺操做是否還在運行中:IsBusy

代碼:

        private void BtnStart_Click(object sender, EventArgs e)
        {
            if (!bgWorker1.IsBusy)
            {
                bgWorker1.RunWorkerAsync();
            }
        }

小結一下:

BackgroundWorker的屬性

IsBusy:獲取一個值(true/false),指示 BackgroundWorker 是否正在運行異步操做。

WorkerReportsProgress:獲取或設置一個值(true/false),該值指示 BackgroundWorker 可否報告進度更新。

WorkerSupportsCancellation:獲取或設置一個值(true/false),該值指示 BackgroundWorker 是否支持異步取消。

BackgroundWorker事件

DoWork:調用 RunWorkerAsync 時發生。(要在後臺進行的耗時操做,不能操做任何用戶界面對象。)

ProgressChanged:調用 ReportProgress 時發生。(後臺操做進行時,隨時向用戶界面報告進度,能夠操做用戶界面對象,如進度條顯示,文本顯示等。)

RunWorkerCompleted:當後臺操做已完成、被取消或引起異常時發生。(能夠操做用戶界面對象,如顯示完成的結果報告。)

 

  

進階二:先後臺交互,參數傳遞

 
先來看兩個問題,再來處理
一、BackgroundWorker的ProgressChanged向用戶界面報告的ProgressPercentage,只是一個百分比,這個百分比數只能是 int 0-100 ,咱們前面的例子中是從0循環到100,到100是恰好100%完成,若是咱們要循環的是從0-50,或0-200,或其它不肯定的數呢?把DoWork中for (int i = 0; i <= 100; i++)的100,改爲50或200試試?
        private void BgWorker1_DoWork(object sender, DoWorkEventArgs e)
        {
            for (int i = 0; i <= 50; i++)
            {
                if (bgWorker1.CancellationPending == true)
                {
                    e.Cancel = true;
                    break; 
                }
                else
                {
                    bgWorker1.ReportProgress(i*2);
                    System.Threading.Thread.Sleep(100);
                }
            }
        }

  看for (int i = 0; i <= 50; i++)以及bgWorker1.ReportProgress(i*2); 緣由就本身琢磨一下了。

 
二、把參數傳遞給後臺異步操做,以及在後臺運算過程當中實時的把信息在前臺用戶界面UI上顯示出來。
爲方便展現,咱們在上面的示例中加入一個名爲txtStatus的TextBox。
 
 先來看看MSDN的說法: RunWorkerAsync方法能夠接受一個 object 類型的參數。
使用的方法:
BtnStart啓動後臺異步操做時讓RunWorkerAsync方法帶一個參數進去。
        private void BtnStart_Click(object sender, EventArgs e)
        {
            if (!bgWorker1.IsBusy)
            {
                int intStart = 1000000;
                bgWorker1.RunWorkerAsync(intStart);
            }
        }

   DoWork事件經過參數 e 的Argument屬性來獲取傳遞過來的參數。

private void BgWorker1_DoWork(object sender, DoWorkEventArgs e)
{
            int intStart = 0;
            if (e.Argument != null)
            {
                intStart = (int)e.Argument;
            }
}

  記得傳過來的是個object類型的參數,使用時必定不要忘記了轉換一下。

 
如今咱們把這個傳過來的參數,在後臺DoWork中處理一下,再把處理的實時的結果反饋到用戶界面上來。
這裏要記得:因爲DoWork事件內部的代碼運行在非UI線程之上,確保在 DoWork 事件處理程序中不操做任何用戶界面對象。
因此分兩個地方處理:
        private void BgWorker1_DoWork(object sender, DoWorkEventArgs e)
        {
            int intStart = 0;
            if (e.Argument != null)
            {
                intStart = (int)e.Argument;
            }
            int intSum = intStart;
            for (int i = 0; i <= 50; i++)
            {
                if (bgWorker1.CancellationPending == true)
                {
                    e.Cancel = true;
                    break; 
                }
                else
                {
                    intSum += i;
                    bgWorker1.ReportProgress(i*2,"循環求和結果:"+intSum.ToString());
                    System.Threading.Thread.Sleep(100);
                }
            }
        }

        private void BgWorker1_ProgressChanged(object sender, ProgressChangedEventArgs e)
        {
            this.pgbPercent.Value = e.ProgressPercentage;
            this.lblPercent.Text = @"已完成:" + e.ProgressPercentage.ToString() + @"%";
            this.txtStatus.Text += e.UserState.ToString() + System.Environment.NewLine;
        }

  BackgroundWorker的DoWork中ReportProgress加一參數,把須要傳出去的參數經過 bgWorker1.ReportProgress(完成百分比, 傳出參數) 報告出去,ProgressChanged中把這個傳出來的參數顯示到UI上:this.txtStatus.Text += e.UserState.ToString() ...。

 如今運行一下程序試試看。

 

 MSDN上說「因爲DoWork事件內部的代碼運行在非UI線程之上,確保在 DoWork 事件處理程序中不操做任何用戶界面對象。而應該經過 ProgressChanged 和 RunWorkerCompleted 事件與用戶界面進行通訊。」上面例子也是按MSDN的套路來的,但我我的對這句話的理解是:DoWork不能操做UI,DoWork操做會引起ProgressChanged,ProgressChanged中能夠操做UI。那麼,我能夠在DoWork的外面定義一個變量,從外面傳進去給它做爲DoWork初始用,DoWork過程當中操做修改它,而後ProgressChanged再把這個變量傳出來,呈現到UI上。
下面來看看,代碼以下:
 
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace bgwB
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        string strBgw = "";
        private void Form1_Load(object sender, EventArgs e)
        {
            bgWorker1.WorkerReportsProgress = true;
            bgWorker1.WorkerSupportsCancellation = true;
        }

        private void BgWorker1_DoWork(object sender, DoWorkEventArgs e)
        {
            int intStart = 0;
            if (e.Argument != null)
            {
                intStart = (int)e.Argument;
            }
            int intSum = intStart;
            for (int i = 0; i <= 50; i++)
            {
                if (bgWorker1.CancellationPending == true)
                {
                    e.Cancel = true;
                    break; 
                }
                else
                {
                    strBgw = "times:" + i.ToString() + "  " + DateTime.Now.ToString();
                    intSum += i;
                    bgWorker1.ReportProgress(i*2,"循環求和結果:"+intSum.ToString());
                    System.Threading.Thread.Sleep(100);

                }
            }
        }

        private void BgWorker1_ProgressChanged(object sender, ProgressChangedEventArgs e)
        {
            this.pgbPercent.Value = e.ProgressPercentage;
            this.lblPercent.Text = @"已完成:" + e.ProgressPercentage.ToString() + @"%";
            this.txtStatus.Text += e.UserState.ToString() + System.Environment.NewLine;
            this.txtStatus.Text += strBgw + System.Environment.NewLine;
        }

        private void BgWorker1_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
        {
            this.lblPercent.Text = "後臺操做結束(多是程序100%完成,也多是用戶取消或程序異常致使結束)。";
        }

        private void BtnStart_Click(object sender, EventArgs e)
        {            
            if (!bgWorker1.IsBusy)
            {
                this.txtStatus.Text = "";
                int intStart = 1000000;
                bgWorker1.RunWorkerAsync(intStart);
            }
        }

        private void BtnCancel_Click(object sender, EventArgs e)
        {
            bgWorker1.CancelAsync();
        }
    }
}

  結果如圖:

 我的感受用這個方法傳參數更爲靈活。
 

總結

至此,BackgroundWorker就基本介紹完了,總的來講,BackgroundWorker用來處理異步處理耗時操做是很是方便快捷的。
 
 
 
 附上這兩個源碼:
相關文章
相關標籤/搜索