需求分析

经常用到的耗时操作,例如:

1、文件下载和上载(包括点对点应用程序传输文件,从网络下载文件、图像等)
2、数据库事务(从数据库读到大量的数据到WinForm界面中的DataGridview里呈现)
3、复杂的本地计算
4、本地磁盘文件访问(读写文件,磁盘文件列表)
……

这些操作在长时间运行时会导致用户界面 (UI) 处于停止响应状态,用户在这操作期间无法进行其他的操作,造成非常差的用户体验,为了不使UI层处于停止响应状态,则可以使用 BackgroundWorker 类方便地解决这类问题。这个后台的线程处理,可以很好的实现常规操作的同时,还可以及时通知UI当前处理信息的进度等。


MSDN的介绍

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

若要在后台执行耗时的操作,请创建一个 BackgroundWorker,侦听那些报告操作进度并在操作完成时发出信号的事件。 可以通过编程方式创建 BackgroundWorker,也可以将它从“工具箱”的“组件”选项卡中拖到窗体上。 如果在 Windows 窗体设计器中创建 BackgroundWorker,则它会出现在组件栏中,而且它的属性会显示在“属性”窗口中。

若要为后台操作做好准备,请添加 DoWork 事件的事件处理程序。 在此事件处理程序中调用耗时的操作。 若要开始此操作,请调用 RunWorkerAsync。 若要收到进度更新的通知,请处理 ProgressChanged 事件。 若要在操作完成时收到通知,请处理 RunWorkerCompleted 事件。

有2点需要注意的:

1、由于DoWork事件内部的代码运行在非UI线程之上,确保在 DoWork 事件处理程序中不操作任何用户界面对象。 而应该通过 ProgressChanged 和 RunWorkerCompleted 事件与用户界面进行通信。
2、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。

 C#之BackgroundWorker从简单入门到深入精通的用法总结-LMLPHP

然后,代码:

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.

 C#之BackgroundWorker从简单入门到深入精通的用法总结-LMLPHP

进阶一:

 重复上面说到的注意点:由于DoWork事件内部的代码运行在非UI线程之上,确保在 DoWork 事件处理程序中不操作任何用户界面对象。 而应该通过 ProgressChanged 和 RunWorkerCompleted 事件与用户界面进行通信。

接下来对上面的程序进行扩展一下:

如何在程序运行途中取消正在进行的运算?

要实现在这个功能,我们首先在程序界面上添加一个可以随时中止后台进程的按钮BtnCancel,允许用户在执行过程中取消当前的操作:

与WorkerReportsProgress属性一样,如果要支持取消操作我们需要设置 WorkerSupportsCancellation属性为 true,还要在DoWork方法中进行支持。

进阶二:

持续更新中。。。
08-16 21:43