微軟提供了一個快捷使用多線程的幫助類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>
后台代碼:
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 }
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 }
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 }
只有兩個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 }
也只有兩個屬性,都是只讀的,一個為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 }
但從父類繼承過來的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 }
所以,RunWorkerCompletedEventArgs參數主要關心三個屬性,Error、Cancelled、Result (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 }
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 }

