backgroundworker

微笑、不失礼 提交于 2020-01-23 05:22:40

BackgroundWorker是.net里用来执行多线程任务的控件,它允许编程者在一个单独的线程上执行一些操作。耗时的操作(如下载和数据库事 务)在长时间运行时可能会导致用户界面 (UI) 始终处于停止响应状态。如果您需要能进行响应的用户界面,而且面临与这类操作相关的长时间延迟,则可以使用 BackgroundWorker类方便地解决问题。   

该控件有三个事件:   DoWork 、ProgressChanged 和 RunWorkerCompleted   在程序中调用RunWorkerAsync方法则会启动DoWork事件的事件处理,当在事件 处理过程中,调用 ReportProgress方法则会启动ProgressChanged事件的事件处理,而当DoWork事件处理完成时,则会触发 RunWorkerCompleted事件。   您必须非常小心,确保在 DoWork 事件处理程序中不操作任何用户界面对象。而应该通过 ProgressChanged和 RunWorkerCompleted 事件与用户界面进行通信。

编辑本段DoWork 事件

  //以C++为例(现在C++.net的书比较少,所以照顾一下,都以C++为例。不过其它语言的也可以看,差不太多)   void backgroundWorker1_DoWork( Object^ sender, DoWorkEventArgs^ e )   其中句柄sender指向的就是该BackgroundWorker控件。   第二个参数e有三个属性,Argument,Cancel和Result。   Argument:   大家应该还记得如何触发DoWork事件吧?对了,就是在程序中调用 RunWorkerAsync方法,RunWorkerAsync方法有两种重载,第一种是无参形式,第二种是有一个指向Object^类型的参数,如果 你调用的是有参类型的RunWorkerAsync,则DoWork事件处理程序的第二个参数e的Argment属性将会返回一个指向你传递过来的这个参 数。   Cancel:   DoWork 事件处理程序中的代码应定期检查 CancellationPending属性值,并在该值为 true 时中止操作。出现这种情况时,可以将 System.ComponentModel.DoWorkEventArgs 的 Cancel标志设置为 true,同时将 RunWorkerCompleted 事件处理程序中的 System.ComponentModel.RunWorkerCompletedEventArgs的 Cancelled 标志设置为 true。   Result:  等下面讲到RunWorkerCompleted事件时再细说。

编辑本段RunWorkerCompleted事件

  当DoWork事件处理完成之后,将会触发该事件。   void backgroundWorker1_RunWorkerCompleted( Object^ sender, RunWorkerCompletedEventArgs^ e )   现在主要讲第二个参数e,最重要的属性是Result。   在DoWork事件中,你将Result设置成什么,这里的Result就返回什么。

编辑本段ProgressChanged事件

  在DoWork事件的处理过程中,如果调用ReportProgress则会发生该事件。   void backgroundWorker1_ProgressChanged( Object^ sender, ProgressChangedEventArgs^ e )   先来说说ReportProgress的两种重载:   void ReportProgress(int percentProgress)   void ReportProgress(int percentProgress,Object^ userState)   ProgressChanged事件处理程序的第二个参数e有一个 ProgressPercentage属性,它就由ReportProgress的第一个参数percentProgress来提供。这个参数一般用来报 告该后台操作完成的进度,然后用ProgressChanged的第二个参数e的ProgressPercentage属性来获取该进度信息。如果用户还 想传递更多的信息,可以使用ReportProgress的第二种重载,它的第二个参数userState将会传递给ProgressChanged事件 的参数e的UserState属性。   好,下面来一段MSDN上的例子:

 

____________________________________________________________________________

在最近的一个Smart Client项目中,为了演示异步操作的实现,写了一个基于BackgorundWorker的例子。由于这个理基本上实现了 BackgorundWorker的大部分功能:异步操作的启动、操作结束后的回调、异步操作的撤销和进度报告等等。尽管没有太多的技术含量,姑且放上来 与大家分享。

一、场景描述

下面是程序运行时的截图。本程序模拟这样的一个场景:有两组相互独立的数据需要逐条获取和显示,左边和右边两个 groupbox分别代表基于这两组数据的操作,由于他们完全独立,因此可以并行执行。当点击Start按钮,以异步的方式从存储介质中逐条获取数据,并 将获取的数据追加到对应的ListBox中,ProgressBar真实反映以获取的数据条数和总记录条数的百分比,同时,当前获取的条数也会在下方的 Label上随着操作的继续而动态变化。此外通过点击Stop按钮,可以中止掉当前的操作。当操作被中止后,ProgressBar和Label反映中止 的那一刻的状态。

background._01_01

二、代码实现

由于界面上左右两边是两个互不干扰、相互独立的操作,所以分别创建了两个BackgroundWorker组件来负责(如下图:backgroundWorkerLeft和backgroundWorkerRight)。

background._01_2

将两个BackgroundWorker的WorkerReportsProgress和WorkerSupportsCancellation设为true。

background._01_03

我们假设获取的记录数固定,我们为此定义一个常量:

   1: private static int MaxRecords = 100;

下面是左边Start按钮的Click event handler:

   1: private void buttonStartLeft_Click(object sender, EventArgs e)
   2: {
   3:     if (this.backgroundWorkerLeft.IsBusy)
   4:     {
   5:         return;
   6:     }
   7:     this.listBoxLeft.Items.Clear();
   8:     this.backgroundWorkerLeft.RunWorkerAsync(MaxRecords);
   9:     this.buttonStartLeft.Enabled = false;
  10:     this.buttonCacnelLeft.Enabled = true;
  11: } 

当Start按钮被点击后,RunWorkerAsync方法被掉调用,我们定义的常量(MaxRecords )当作参数被掺入。随后,将会触发其DoWork事件,Dowork event handler处理代码如下:

   1: private void backgroundWorkerLeft_DoWork(object sender, DoWorkEventArgs e)
   2: {
   3:     try
   4:     {
   5: e.Result = this.RetrieveData(this.backgroundWorkerLeft, e);
   6:     }
   7:     catch (Exception ex)
   8:     {
   9:        MessageBox.Show(ex.Message);
  10:        throw;
  11:     }
  12: } 

调用RetrieveData方法逐条获取数据。注意该方法的两个参数:BackgroundWorker和 DoWorkEventArgs 对象,返回值是返回数据的数量。由于在buttonStartLeft_Click中,我们将常量MaxRecords 作为参数传入了BackgroundWorker的RunWorkerAsync方法, 此时的e.Argument = MaxRecords。之所以要将这两个参数传入RetrieveData()方法,是因为该方法是为两个BackgroundWorker服务的,需要 通过参数来区别当前是哪个BackgroundWorker。我们再来看看RetrieveData方法的定义:

   1: private int RetrieveData(BackgroundWorker worker, DoWorkEventArgs e)
   2: {
   3:     int maxRecords = (int)e.Argument;
   4:     int percent = 0;
   5:     for (int i = 1; i <= maxRecords; i++)
   6:     {
   7:         if (worker.CancellationPending)
   8:         {
   9:             return i;
  10:         } 
  11:  
  12:         percent = (int)(((double)i / (double)maxRecords) * 100);
  13:         worker.ReportProgress(percent, new KeyValuePair<int,string>(i,Guid.NewGuid().ToString()));
  14:         Thread.Sleep(100);
  15:     } 
  16:  
  17:     return maxRecords;
  18: } 

通过e.Argument,获得最大数据获取量之后,进行一个for循环,在每次迭代中,如何 worker.CancellationPending==true,代表异步操作被显示取消,则直接返回;否则,调用BackgroundWorker 的ReportProgress方法。ReportProgress具有两个重载:

  • public void ReportProgress(int percentProgress);
  • public void ReportProgress(int percentProgress, object userState);

percentProgress代表当前进度,从0-100。userState便于传入一些额外的参数。在界面 上,由于数据的当前数量需要实时地显示,而记录也是现取现加(取出一条就在ListBox上追加)。所以制定一个 KeyValuePair<int,string>对象作为第二个参数。其中Key为当前记录数,Value是一个Guid,代表取出的数 据。

ReportProgress的调用将会导致ProgressChanged事件被触发。ProgressChanged event handler用于显示当前进度、当前记录数量和显示获取的纪录:

   1: private void backgroundWorkerLeft_ProgressChanged(object sender, ProgressChangedEventArgs e)
   2: {
   3:     KeyValuePair<int,string> record = (KeyValuePair<int,string>) e.UserState ;
   4:     this.labelResultLeft.Text = string.Format("There are {0} records retrieved!", record.Key);
   5:     this.progressBarLeft.Value = e.ProgressPercentage;
   6:     this.listBoxLeft.Items.Add(record.Value);
   7: }
注:这些操作需要操作UI上的控件,只能在Main Thread中进行。如何在RetrieveData方法进行的话,由于该方式是一个异步方法,是会抛出异常的。

由于操作的时间可能无法预知,在长时间不能完全获取数据的情况下,用户可以需要手工结束掉当前的操作。这个操作实现在Stop按钮的Click事件中:

   1: private void buttonCacnelLeft_Click(object sender, EventArgs e)
   2: {
   3:     this.backgroundWorkerLeft.CancelAsync();
   4: } 
如何操作正常地结束,BackgroundWorker的RunWorkerCompleted会被触发,下面是RunWorkerCompleted

event handler的定义:

   1: private void backgroundWorkerLeft_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
   2: {
   3:     try
   4:     {
   5:         this.labelResultLeft.Text = string.Format("Total records: {0}", e.Result);
   6:         this.buttonStartLeft.Enabled = true;
   7:         this.buttonCacnelLeft.Enabled = false;
   8:     }
   9:     catch (TargetInvocationException ex)
  10:     {
  11:         MessageBox.Show(ex.InnerException.GetType().ToString());
  12:     }
  13: }

上面介绍的是界面左边功能的实现,右边部分的实现完全一致。干兴趣的朋友可以参考Source Code.

在最近的一个Smart Client项目中,为了演示异步操作的实现,写了一个基于BackgorundWorker的例子。由于这个理基本上实现了 BackgorundWorker的大部分功能:异步操作的启动、操作结束后的回调、异步操作的撤销和进度报告等等。尽管没有太多的技术含量,姑且放上来 与大家分享。

一、场景描述

下面是程序运行时的截图。本程序模拟这样的一个场景:有两组相互独立的数据需要逐条获取和显示,左边和右边两个 groupbox分别代表基于这两组数据的操作,由于他们完全独立,因此可以并行执行。当点击Start按钮,以异步的方式从存储介质中逐条获取数据,并 将获取的数据追加到对应的ListBox中,ProgressBar真实反映以获取的数据条数和总记录条数的百分比,同时,当前获取的条数也会在下方的 Label上随着操作的继续而动态变化。此外通过点击Stop按钮,可以中止掉当前的操作。当操作被中止后,ProgressBar和Label反映中止 的那一刻的状态。

background._01_01

二、代码实现

由于界面上左右两边是两个互不干扰、相互独立的操作,所以分别创建了两个BackgroundWorker组件来负责(如下图:backgroundWorkerLeft和backgroundWorkerRight)。

background._01_2

将两个BackgroundWorker的WorkerReportsProgress和WorkerSupportsCancellation设为true。

background._01_03

我们假设获取的记录数固定,我们为此定义一个常量:

   1: private static int MaxRecords = 100;

下面是左边Start按钮的Click event handler:

   1: private void buttonStartLeft_Click(object sender, EventArgs e)
   2: {
   3:     if (this.backgroundWorkerLeft.IsBusy)
   4:     {
   5:         return;
   6:     }
   7:     this.listBoxLeft.Items.Clear();
   8:     this.backgroundWorkerLeft.RunWorkerAsync(MaxRecords);
   9:     this.buttonStartLeft.Enabled = false;
  10:     this.buttonCacnelLeft.Enabled = true;
  11: } 

当Start按钮被点击后,RunWorkerAsync方法被掉调用,我们定义的常量(MaxRecords )当作参数被掺入。随后,将会触发其DoWork事件,Dowork event handler处理代码如下:

   1: private void backgroundWorkerLeft_DoWork(object sender, DoWorkEventArgs e)
   2: {
   3:     try
   4:     {
   5: e.Result = this.RetrieveData(this.backgroundWorkerLeft, e);
   6:     }
   7:     catch (Exception ex)
   8:     {
   9:        MessageBox.Show(ex.Message);
  10:        throw;
  11:     }
  12: } 

调用RetrieveData方法逐条获取数据。注意该方法的两个参数:BackgroundWorker和 DoWorkEventArgs 对象,返回值是返回数据的数量。由于在buttonStartLeft_Click中,我们将常量MaxRecords 作为参数传入了BackgroundWorker的RunWorkerAsync方法, 此时的e.Argument = MaxRecords。之所以要将这两个参数传入RetrieveData()方法,是因为该方法是为两个BackgroundWorker服务的,需要 通过参数来区别当前是哪个BackgroundWorker。我们再来看看RetrieveData方法的定义:

   1: private int RetrieveData(BackgroundWorker worker, DoWorkEventArgs e)
   2: {
   3:     int maxRecords = (int)e.Argument;
   4:     int percent = 0;
   5:     for (int i = 1; i <= maxRecords; i++)
   6:     {
   7:         if (worker.CancellationPending)
   8:         {
   9:             return i;
  10:         } 
  11:  
  12:         percent = (int)(((double)i / (double)maxRecords) * 100);
  13:         worker.ReportProgress(percent, new KeyValuePair<int,string>(i,Guid.NewGuid().ToString()));
  14:         Thread.Sleep(100);
  15:     } 
  16:  
  17:     return maxRecords;
  18: } 

通过e.Argument,获得最大数据获取量之后,进行一个for循环,在每次迭代中,如何 worker.CancellationPending==true,代表异步操作被显示取消,则直接返回;否则,调用BackgroundWorker 的ReportProgress方法。ReportProgress具有两个重载:

  • public void ReportProgress(int percentProgress);
  • public void ReportProgress(int percentProgress, object userState);

percentProgress代表当前进度,从0-100。userState便于传入一些额外的参数。在界面 上,由于数据的当前数量需要实时地显示,而记录也是现取现加(取出一条就在ListBox上追加)。所以制定一个 KeyValuePair<int,string>对象作为第二个参数。其中Key为当前记录数,Value是一个Guid,代表取出的数 据。

ReportProgress的调用将会导致ProgressChanged事件被触发。ProgressChanged event handler用于显示当前进度、当前记录数量和显示获取的纪录:

   1: private void backgroundWorkerLeft_ProgressChanged(object sender, ProgressChangedEventArgs e)
   2: {
   3:     KeyValuePair<int,string> record = (KeyValuePair<int,string>) e.UserState ;
   4:     this.labelResultLeft.Text = string.Format("There are {0} records retrieved!", record.Key);
   5:     this.progressBarLeft.Value = e.ProgressPercentage;
   6:     this.listBoxLeft.Items.Add(record.Value);
   7: }
注:这些操作需要操作UI上的控件,只能在Main Thread中进行。如何在RetrieveData方法进行的话,由于该方式是一个异步方法,是会抛出异常的。

由于操作的时间可能无法预知,在长时间不能完全获取数据的情况下,用户可以需要手工结束掉当前的操作。这个操作实现在Stop按钮的Click事件中:

   1: private void buttonCacnelLeft_Click(object sender, EventArgs e)
   2: {
   3:     this.backgroundWorkerLeft.CancelAsync();
   4: } 
如何操作正常地结束,BackgroundWorker的RunWorkerCompleted会被触发,下面是RunWorkerCompleted

event handler的定义:

   1: private void backgroundWorkerLeft_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
   2: {
   3:     try
   4:     {
   5:         this.labelResultLeft.Text = string.Format("Total records: {0}", e.Result);
   6:         this.buttonStartLeft.Enabled = true;
   7:         this.buttonCacnelLeft.Enabled = false;
   8:     }
   9:     catch (TargetInvocationException ex)
  10:     {
  11:         MessageBox.Show(ex.InnerException.GetType().ToString());
  12:     }
  13: }

上面介绍的是界面左边功能的实现,右边部分的实现完全一致。干兴趣的朋友可以参考Source Code.

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!