BackgroundWorker簡單實用(簡便的異步操作)


  

微軟提供了一個快捷使用多線程的幫助類BackgroundWorker,能夠快速創建一個新的線程,並能報告進度,暫停,以及在線程完成后處理別的任務。

1.BackgroundWorker類介紹

     1.1. 四個常用屬性:

      public bool IsBusy { get; }    //只讀屬性,用來判斷當前線程是否正在工作中

    public bool WorkerReportsProgress { get; set; }   //決定當前線程是否能報告進度

    public bool WorkerSupportsCancellation { get; set; }   //決定當前線程能否取消

           public bool CancellationPending { get; }    //只讀屬性,用來判斷是否發送了取消線程的消息(當調用CancelAsync()方法時,被設置為true)

     1.2. 三個常用事件:

    public event DoWorkEventHandler DoWork; //開始 必須,線程的主要邏輯,調用RunWorkerAsync()時觸發該事件

    public event ProgressChangedEventHandler ProgressChanged; //報告 可選,報告進度事件,調用ReportProgress()時觸發該事件

    public event RunWorkerCompletedEventHandler RunWorkerCompleted; //結束 可選,當線程運行完畢、發生異常和調用CancelAsync()方法這三種方式都會觸發該事件

      1.3. 三個常用方法:

    public void RunWorkerAsync(); //啟動線程,觸發DoWork事件
    public void RunWorkerAsync(object argument);

    public void ReportProgress(int percentProgress); //報告進度,觸發ProgressChanged事件
    public void ReportProgress(int percentProgress, object userState);

    public void CancelAsync(); //取消線程,將CancellationPending設置為true

2.BackgroundWorker用法

       2.1. 簡單用法:

             新建BackgroundWorder對象;

             根據需求, 設置是否能取消(WorkerSupportsCancellation)、是否報告進度(WorkerReportsProgress);

             根據需求,設置好相關事件,DoWorker、ProgressChanged、ProgressChanged;

             調用RunWorkerAsyns()方法,啟動線程;

             在需要取消的位置,判斷CancellationPending的值,並做相關處理;//可選

             在適當的位置調用ReportProgress(int percentProgress)方法,報告進度。

        2.2.簡單例子:

               2.2.1. 最基本的運行代碼

                 

                 界面的簡單代碼:

                        界面上就一個Grid控件和2個Button

 1 <Window x:Class="BackgroundWorkerDemo20170324.MainWindow"
 2         xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
 3         xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
 4         Title="MainWindow" Height="342" Width="504">
 5     <Grid>
 6         <ProgressBar x:Name="pBar" Margin="50,69,43,209">
 7         </ProgressBar>
 8         <Button x:Name="btnStart" Content="start" Click="btnStart_Click" Margin="50,218,311,63"/>
 9         <Button x:Name="btnCancel" Content="cancel" Click="btnCancel_Click" Margin="285,218,95,63"/>
10     </Grid>
11 </Window>
View Code

                 后台代碼:

 1 using System;
 2 using System.Collections.Generic;
 3 using System.ComponentModel;
 4 using System.Linq;
 5 using System.Text;
 6 using System.Threading;
 7 using System.Windows;
 8 using System.Windows.Controls;
 9 using System.Windows.Data;
10 using System.Windows.Documents;
11 using System.Windows.Input;
12 using System.Windows.Media;
13 using System.Windows.Media.Imaging;
14 using System.Windows.Navigation;
15 using System.Windows.Shapes;
16 
17 namespace BackgroundWorkerDemo20170324
18 {
19     /// <summary>
20     /// Interaction logic for MainWindow.xaml
21     /// </summary>
22     public partial class MainWindow : Window
23     {
24         private BackgroundWorker worker;
25         public MainWindow()
26         {
27             InitializeComponent();
28             worker = new BackgroundWorker();                      //新建BackgroundWorker
29             worker.WorkerReportsProgress = true;                  //允許報告進度
30             worker.WorkerSupportsCancellation = true;             //允許取消線程
31             worker.DoWork += worker_DoWork;                       //設置主要工作邏輯
32             worker.ProgressChanged += worker_ProgressChanged;     //進度變化的相關處理
33             worker.RunWorkerCompleted += worker_RunWorkerCompleted;  //線程完成時的處理
34         }
35 
36         /// <summary>
37         /// 主要工作邏輯
38         /// </summary>
39         /// <param name="sender"></param>
40         /// <param name="e"></param>
41         private void worker_DoWork(object sender, DoWorkEventArgs e)
42         {
43             BackgroundWorker tempWorker = sender as BackgroundWorker;
44             for (int i = 0; i <= 100; i++)
45             {
46                 Thread.Sleep(200);    //避免太快,讓線程暫停一會再報告進度
47                 tempWorker.ReportProgress(i);   //調用ReportProgress()方法報告進度,同時觸發ProgressChanged事件
48             }
49         }
50 
51         /// <summary>
52         /// 處理進度變化,改變進度條的值
53         /// </summary>
54         /// <param name="sender"></param>
55         /// <param name="e"></param>
56         private void worker_ProgressChanged(object sender, ProgressChangedEventArgs e)
57         {
58             pBar.Value = e.ProgressPercentage;
59         }
60 
61         /// <summary>
62         /// 線程完成后的處理
63         /// </summary>
64         /// <param name="sender"></param>
65         /// <param name="e"></param>
66         private void worker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
67         {
68             MessageBox.Show("線程工作完成");
69         }
70 
71         /// <summary>
72         /// 點擊Start按鈕啟動線程
73         /// </summary>
74         /// <param name="sender"></param>
75         /// <param name="e"></param>
76         private void btnStart_Click(object sender, RoutedEventArgs e)
77         {
78             worker.RunWorkerAsync();   //調用該方法才會啟動線程
79         }
80 
81         /// <summary>
82         /// 點擊Cancel按鈕取消線程,但先判斷線程是否正在工作
83         /// </summary>
84         /// <param name="sender"></param>
85         /// <param name="e"></param>
86         private void btnCancel_Click(object sender, RoutedEventArgs e)
87         {
88             if (worker.IsBusy)
89                 worker.CancelAsync();
90             else
91                 MessageBox.Show("There is no thead running now.");
92         }
93     }
94 }
View Code

         2.2.2. 能取消線程

               在需要取消線程的位置判斷CancellationPending屬性,一般在循環體中(因為循環一般耗時居多),判斷當CancellationPending==true時,

  需要將DoWorkEventArgs的Cancel屬性設置為true, 然后就可以在RunWorkerCompleted中判斷RunWorkerCompletedEventArgs的Cancelled

  屬性來進行對應的處理(即,當用戶取消線程時,也會觸發RunWorkerCompleted事件)

          修改后的代碼:

  1 using System;
  2 using System.Collections.Generic;
  3 using System.ComponentModel;
  4 using System.Linq;
  5 using System.Text;
  6 using System.Threading;
  7 using System.Windows;
  8 using System.Windows.Controls;
  9 using System.Windows.Data;
 10 using System.Windows.Documents;
 11 using System.Windows.Input;
 12 using System.Windows.Media;
 13 using System.Windows.Media.Imaging;
 14 using System.Windows.Navigation;
 15 using System.Windows.Shapes;
 16 
 17 namespace BackgroundWorkerDemo20170324
 18 {
 19     /// <summary>
 20     /// Interaction logic for MainWindow.xaml
 21     /// </summary>
 22     public partial class MainWindow : Window
 23     {
 24         private BackgroundWorker worker;
 25         public MainWindow()
 26         {
 27             InitializeComponent();
 28             worker = new BackgroundWorker();                      //新建BackgroundWorker
 29             worker.WorkerReportsProgress = true;                  //允許報告進度
 30             worker.WorkerSupportsCancellation = true;             //允許取消線程
 31             worker.DoWork += worker_DoWork;                       //設置主要工作邏輯
 32             worker.ProgressChanged += worker_ProgressChanged;     //進度變化的相關處理
 33             worker.RunWorkerCompleted += worker_RunWorkerCompleted;  //線程完成時的處理
 34         }
 35 
 36         /// <summary>
 37         /// 主要工作邏輯
 38         /// </summary>
 39         /// <param name="sender"></param>
 40         /// <param name="e"></param>
 41         private void worker_DoWork(object sender, DoWorkEventArgs e)
 42         {
 43             BackgroundWorker tempWorker = sender as BackgroundWorker;
 44             for (int i = 0; i <= 100; i++)
 45             {
 46                 if (tempWorker.CancellationPending)  //當點擊Cancel按鈕時,CancellationPending被設置為true
 47                 {
 48                     e.Cancel = true;  //此處設置Cancel=true后,就可以在RunWorkerCompleted中判斷e.Cancelled是否為true
 49                     break;
 50                 }
 51                 Thread.Sleep(200);    //避免太快,讓線程暫停一會再報告進度
 52                 tempWorker.ReportProgress(i);   //調用ReportProgress()方法報告進度,同時觸發ProgressChanged事件
 53             }
 54         }
 55 
 56         /// <summary>
 57         /// 處理進度變化,改變進度條的值
 58         /// </summary>
 59         /// <param name="sender"></param>
 60         /// <param name="e"></param>
 61         private void worker_ProgressChanged(object sender, ProgressChangedEventArgs e)
 62         {
 63             pBar.Value = e.ProgressPercentage;
 64         }
 65 
 66         /// <summary>
 67         /// 線程完成后的處理
 68         /// </summary>
 69         /// <param name="sender"></param>
 70         /// <param name="e"></param>
 71         private void worker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
 72         {
 73             if (e.Cancelled)  //被取消時
 74                 MessageBox.Show("線程被取消了");
 75             else              //正常結束   
 76                 MessageBox.Show("線程工作完成");
 77         }
 78 
 79         /// <summary>
 80         /// 點擊Start按鈕啟動線程
 81         /// </summary>
 82         /// <param name="sender"></param>
 83         /// <param name="e"></param>
 84         private void btnStart_Click(object sender, RoutedEventArgs e)
 85         {
 86             worker.RunWorkerAsync();   //調用該方法才會啟動線程
 87         }
 88 
 89         /// <summary>
 90         /// 點擊Cancel按鈕取消線程,但先判斷線程是否正在工作
 91         /// </summary>
 92         /// <param name="sender"></param>
 93         /// <param name="e"></param>
 94         private void btnCancel_Click(object sender, RoutedEventArgs e)
 95         {
 96             if (worker.IsBusy)
 97                 worker.CancelAsync();
 98             else
 99                 MessageBox.Show("There is no thead running now.");
100         }
101     }
102 }
View Code

 

3.線程中的傳值

      3.1. 與BackgroundWorker相關的三個參數類

    //
        // 摘要: 
        //     引發 System.ComponentModel.BackgroundWorker.DoWork 事件。
        //
        // 參數: 
        //   e:
        //     包含事件數據的 System.EventArgs。
        protected virtual void OnDoWork(DoWorkEventArgs e);
        //
        // 摘要: 
        //     引發 System.ComponentModel.BackgroundWorker.ProgressChanged 事件。
        //
        // 參數: 
        //   e:
        //     包含事件數據的 System.EventArgs。
        protected virtual void OnProgressChanged(ProgressChangedEventArgs e);
        //
        // 摘要: 
        //     引發 System.ComponentModel.BackgroundWorker.RunWorkerCompleted 事件。
        //
        // 參數: 
        //   e:
        //     包含事件數據的 System.EventArgs。
        protected virtual void OnRunWorkerCompleted(RunWorkerCompletedEventArgs e);

        通過上面的代碼我們可以看到,BackgroundWorker的三個常用事件都有與之對應的參數類:

             3.1.1. DoWorkEventArgs 為DoWork事件提供數據,詳細代碼如下:

 1 #region 程序集 System.dll, v2.0.0.0
 2 // C:\Windows\Microsoft.NET\Framework\v2.0.50727\System.dll
 3 #endregion
 4 
 5 using System;
 6 
 7 namespace System.ComponentModel
 8 {
 9     // 摘要: 
10     //     為 System.ComponentModel.BackgroundWorker.DoWork 事件處理程序提供數據。
11     public class DoWorkEventArgs : CancelEventArgs
12     {
13         // 摘要: 
14         //     初始化 System.ComponentModel.DoWorkEventArgs 類的新實例。
15         //
16         // 參數: 
17         //   argument:
18         //     指定異步操作的參數。
19         public DoWorkEventArgs(object argument);
20 
21         // 摘要: 
22         //     獲取表示異步操作參數的值。
23         //
24         // 返回結果: 
25         //     表示異步操作參數的 System.Object。
26         public object Argument { get; }
27         //
28         // 摘要: 
29         //     獲取或設置表示異步操作結果的值。
30         //
31         // 返回結果: 
32         //     表示異步操作結果的 System.Object。
33         public object Result { get; set; }
34     }
35 }
View Code

     只有兩個object類型的屬性Argument(只讀的)和Result,

          因為調用RunWorkerAsync()方法,就會觸發DoWork事件,細心的你會發現,RunWorkerAsync()方法有兩個重載,

          帶有參數的RunWorkerAsync(object argument),這個形參argument就是傳遞給DoWorkEventArgs的Arument屬性的

          而Result屬性是作為DoWork事件的結果傳遞給RunWorkerCompletedEventArgs的Result屬性,

          

             3.1.2.  ProgressChangedEventArgs 為ProgressChanged事件提供數據,詳細代碼如下:

 1 using System;
 2 
 3 namespace System.ComponentModel
 4 {
 5     // 摘要: 
 6     //     為 System.ComponentModel.BackgroundWorker.ProgressChanged 事件提供數據。
 7     public class ProgressChangedEventArgs : EventArgs
 8     {
 9         // 摘要: 
10         //     初始化 System.ComponentModel.ProgressChangedEventArgs 類的新實例。
11         //
12         // 參數: 
13         //   progressPercentage:
14         //     已完成的異步任務的百分比。
15         //
16         //   userState:
17         //     唯一的用戶狀態。
18         public ProgressChangedEventArgs(int progressPercentage, object userState);
19 
20         // 摘要: 
21         //     獲取異步任務的進度百分比。
22         //
23         // 返回結果: 
24         //     指示異步任務進度的百分比值。
25         public int ProgressPercentage { get; }
26         //
27         // 摘要: 
28         //     獲取唯一的用戶狀態。
29         //
30         // 返回結果: 
31         //     指示用戶狀態的唯一 System.Object。
32         public object UserState { get; }
33     }
34 }
View Code

             也只有兩個屬性,都是只讀的,一個為int類型的ProgerssPercentage,表示任務進度百分百;另一個是object類型的UserState

             這兩個參數都是通過ReportProgress()方法傳入,由於UserState屬性時object類型的,所以當需要實現復制邏輯時,可以自定義一個類型

 

             3.1.3. RunWorkerCompletedEventArgs 稍微特殊一點,雖然該類的直接定義中只有兩個屬性,

            object類型的兩個只讀屬性Result和UserState

 1 namespace System.ComponentModel
 2 {
 3     public class RunWorkerCompletedEventArgs : AsyncCompletedEventArgs
 4     {
 5         public RunWorkerCompletedEventArgs(object result, Exception error, bool cancelled);
 6 
 7         public object Result { get; }
 8         [Browsable(false)]
 9         [EditorBrowsable(EditorBrowsableState.Never)]
10         public object UserState { get; }
11     }
12 }
View Code

            但從父類繼承過來的Cancelled和Error屬性才是重點

 1 namespace System.ComponentModel
 2 {
 3     public class AsyncCompletedEventArgs : EventArgs
 4     {
 5         public AsyncCompletedEventArgs(Exception error, bool cancelled, object userState);
 6 
 7         [SRDescription("Async_AsyncEventArgs_Cancelled")]
 8         public bool Cancelled { get; }
 9         [SRDescription("Async_AsyncEventArgs_Error")]
10         public Exception Error { get; }
11         [SRDescription("Async_AsyncEventArgs_UserState")]
12         public object UserState { get; }
13 
14         protected void RaiseExceptionIfNecessary();
15     }
16 }
View Code

           所以,RunWorkerCompletedEventArgs參數主要關心三個屬性,ErrorCancelledResult (UserState一般很少用到)

           注意:標准的RunWorkerCompleted處理都應該先處理Error和Cancelled情況,否則直接訪問Result時會報錯,

       盡量以這種方式來整理自己的代碼邏輯:

 1 // This event handler deals with the results of the
 2 // background operation.
 3 private void backgroundWorker1_RunWorkerCompleted(
 4     object sender, RunWorkerCompletedEventArgs e)
 5 {
 6     // First, handle the case where an exception was thrown.
 7     if (e.Error != null)
 8     {
 9         MessageBox.Show(e.Error.Message);
10     }
11     else if (e.Cancelled)
12     {
13         // Next, handle the case where the user canceled 
14         // the operation.
15         // Note that due to a race condition in 
16         // the DoWork event handler, the Cancelled
17         // flag may not have been set, even though
18         // CancelAsync was called.
19         resultLabel.Text = "Canceled";
20     }
21     else
22     {
23         // Finally, handle the case where the operation 
24         // succeeded.
25         resultLabel.Text = e.Result.ToString();
26     }
27 }
View Code

 

4.注意事項

      4.1. DoWork事件中不能與UI控件進行交流

              如果需要在線程處理過程中與UI控件進行交流,請在ProgressChanged和RunWorkerCompleted中進行,否則會出現以下錯誤

System.InvalidOperationException was unhandled by user code
  Message=The calling thread cannot access this object because a different thread owns it.
  Source=WindowsBase
  StackTrace:
       at System.Windows.Threading.Dispatcher.VerifyAccess()
       at System.Windows.DependencyObject.SetValue(DependencyProperty dp, Object value)
       at System.Windows.Controls.Primitives.RangeBase.set_Value(Double value)
       at BackgroundWorkerDemo20170324.MainWindow.worker_DoWork(Object sender, DoWorkEventArgs e) in 

 

 

 

 

5.附帶

      5.1. Visual Studio中的API顯示如下:

             代碼:

 1 using System;
 2 
 3 namespace System.ComponentModel
 4 {
 5     [DefaultEvent("DoWork")]
 6     public class BackgroundWorker : Component
 7     {
 8         public BackgroundWorker();
 9 
10         [Browsable(false)]
11         public bool CancellationPending { get; }                          //用於判斷是否取消了線程
12         [Browsable(false)]
13         public bool IsBusy { get; }                                       //判斷當前線程是否正在工作
14         [DefaultValue(false)]
15         public bool WorkerReportsProgress { get; set; }                   //是否報告進度
16         [DefaultValue(false)]
17         public bool WorkerSupportsCancellation { get; set; }              //是否支持取消線程,如果要調用CancelAsyns()方法,必須設置為true
18 
19         public event DoWorkEventHandler DoWork;                           //線程核心代碼,耗時的操作放在這里,調用RunWorkerAsync()時觸發            
20         public event ProgressChangedEventHandler ProgressChanged;         //當進度改變后執行的代碼,調用ReportProgress()時觸發
21         public event RunWorkerCompletedEventHandler RunWorkerCompleted;   //3種情況:當線程完成、手動取消線程時、線程發生異常時觸發
22 
23         public void CancelAsync();                                        //調用該方法會發出取消線程的消息,但並不會立即中止線程
24         protected virtual void OnDoWork(DoWorkEventArgs e);
25         protected virtual void OnProgressChanged(ProgressChangedEventArgs e);
26         protected virtual void OnRunWorkerCompleted(RunWorkerCompletedEventArgs e);
27         public void ReportProgress(int percentProgress);                  //調用該方法就觸發ProgressChanged事件,相當於ReportProgress(int percentProgress, null)
28         public void ReportProgress(int percentProgress, object userState); 
29         public void RunWorkerAsync();                                     //調用該方法啟動線程,同時觸發DoWork事件,相當於RunWorkerAsync(null)
30         public void RunWorkerAsync(object argument);
31     }
32 }
View Code

 

 

 

 

     5.2. 相關博客推薦,非常優秀,值得查看:

               [你必須知道的異步編程]——基於事件的異步編程模式 ;

               http://m.blog.csdn.net/article/details?id=7291070 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2026 CODEPRJ.COM