初学者 BackgroundWorker 类示例






4.93/5 (101投票s)
多线程应用程序的简单步骤
引言
本文向初级的.NET开发者介绍如何开发多线程应用程序,而无需承受多线程带来的复杂性。
背景
一个基本的Windows应用程序通常在称为UI线程的单个线程上运行。这个UI线程负责创建/绘制所有控件,并且代码在此线程上执行。因此,当您运行一个长时间运行的任务时(例如,数据密集型的数据库操作或处理数百个位图图像),UI线程将被锁定,UI应用程序将变白(请记住,UI线程负责绘制所有控件),导致您的应用程序处于“未响应”状态。
 
 
Using the Code
您需要做的是将繁重的处理转移到另一个线程上。
 
 
让UI线程空闲以绘制UI。.NET为我们提供了BackgroundWorker对象来简化多线程。这个对象设计用于在另一个线程上运行一个函数,并在完成后在UI线程上调用一个事件。
步骤非常简单
- 创建一个BackgroundWorker对象。
- 告诉BackgroundWorker对象要在后台线程上运行的任务(DoWork函数)。
- 告诉它在工作完成后要在UI线程上运行的函数(RunWorkerCompleted函数)。
BackgroundWorker使用线程池,它会回收线程以避免为每个新任务重新创建它们。这意味着永远不要对BackgroundWorker线程调用Abort。
以及一条永远不要忘记的黄金法则
永远不要在没有创建它们的线程上访问UI对象。这意味着您不能使用如下代码...
lblStatus.Text = "Processing file...20%";
...在DoWork函数中。如果您这样做,将会收到运行时错误。BackgroundWorker对象通过提供ReportProgress函数来解决此问题,该函数可以从后台线程的DoWork函数中调用。这将导致ProgressChanged事件在UI线程上触发。现在我们可以访问UI对象的线程并执行我们想要的操作(在我们的例子中,设置状态标签文本)。
BackgroundWorker还提供了一个RunWorkerCompleted事件,该事件在DoWork事件处理程序完成其工作后触发。处理RunWorkerCompleted不是强制性的,但通常会这样做以查询在DoWork中抛出的任何异常。此外,RunWorkerCompleted事件处理程序中的代码能够更新Windows Forms和WPF控件,而无需显式封送;DoWork事件处理程序中的代码则不能。
添加对进度报告的支持
- 将WorkerReportsProgress属性设置为true。
- 在DoWork事件处理程序中,定期调用ReportProgress,并提供一个“完成百分比”值。
 m_oWorker.ReportProgress(i); //as seen in the code 
- 处理ProgressChanged事件,查询其事件参数的ProgressPercentage属性。
 void m_oWorker_ProgressChanged(object sender, ProgressChangedEventArgs e) { // This function fires on the UI thread so it's safe to edit // the UI control directly, no funny business with Control.Invoke :) // Update the progressBar with the integer supplied to us from the // ReportProgress() function. progressBar1.Value = e.ProgressPercentage; lblStatus.Text = "Processing......" + progressBar1.Value.ToString() + "%"; }
ProgressChanged事件处理程序中的代码可以像RunWorkerCompleted一样自由地与UI控件交互。通常,您会在这里更新进度条。
添加对取消的支持
- 将WorkerSupportsCancellation属性设置为true。
- 在DoWork事件处理程序中,定期检查CancellationPending属性——如果为true,则将事件参数的Cancel属性设置为true,然后返回。(如果工作者认为任务太难而无法继续,它可以在没有提示的情况下设置Cancel为true并退出,而无需通过CancellationPending。)if (m_oWorker.CancellationPending) { // Set the e.Cancel flag so that the WorkerCompleted event // knows that the process was cancelled. e.Cancel = true; m_oWorker.ReportProgress(0); return; }
- 调用CancelAsync请求取消。此代码会在按下“取消”按钮时得到处理。
 m_oWorker.CancelAsync(); 
属性
这不是一个详尽的列表,但我需要强调Argument、Result和RunWorkerAsync方法。这些是BackgroundWorker的属性,您需要了解它们才能完成任何工作。我将属性显示为您在代码中引用它们的方式。
- DoWorkEventArgs e用法:包含- e.Argument和- e.Result,因此用于访问这些属性。
- e.Argument用法:用于获取- RunWorkerAsync接收的参数引用。
- e.Result用法:检查- BackgroundWorker处理的结果。
- m_oWorker.RunWorkerAsync();用法:调用以在工作线程上启动进程。
这是完整的代码
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Threading;
using System.Text;
using System.Windows.Forms;
namespace BackgroundWorkerSample
{
    // The BackgroundWorker will be used to perform a long running action
    // on a background thread.  This allows the UI to be free for painting
    // as well as other actions the user may want to perform.  The background
    // thread will use the ReportProgress event to update the ProgressBar
    // on the UI thread.
    public partial class Form1 : Form
    {
        /// <summary>
        /// The backgroundworker object on which the time consuming operation 
        /// shall be executed
        /// </summary>
        BackgroundWorker m_oWorker;
        public Form1()
        {
            InitializeComponent();
            m_oWorker = new BackgroundWorker();
    
            // Create a background worker thread that ReportsProgress &
            // SupportsCancellation
            // Hook up the appropriate events.
            m_oWorker.DoWork += new DoWorkEventHandler(m_oWorker_DoWork);
            m_oWorker.ProgressChanged += new ProgressChangedEventHandler
					(m_oWorker_ProgressChanged);
            m_oWorker.RunWorkerCompleted += new RunWorkerCompletedEventHandler
					(m_oWorker_RunWorkerCompleted);
            m_oWorker.WorkerReportsProgress = true;
            m_oWorker.WorkerSupportsCancellation = true;
        }
        /// <summary>
        /// On completed do the appropriate task
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        void m_oWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
        {
            // The background process is complete. We need to inspect
            // our response to see if an error occurred, a cancel was
            // requested or if we completed successfully.  
            if (e.Cancelled)
            {
                lblStatus.Text = "Task Cancelled.";
            }
            // Check to see if an error occurred in the background process.
            else if (e.Error != null)
            {
                lblStatus.Text = "Error while performing background operation.";
            }
            else
            {  
                // Everything completed normally.
                lblStatus.Text = "Task Completed...";
            }
            //Change the status of the buttons on the UI accordingly
            btnStartAsyncOperation.Enabled = true;
            btnCancel.Enabled = false;
        }
        /// <summary>
        /// Notification is performed here to the progress bar
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        void m_oWorker_ProgressChanged(object sender, ProgressChangedEventArgs e)
        {
            // This function fires on the UI thread so it's safe to edit
            // the UI control directly, no funny business with Control.Invoke :)
            // Update the progressBar with the integer supplied to us from the
            // ReportProgress() function.  
            progressBar1.Value = e.ProgressPercentage;
            lblStatus.Text = "Processing......" + progressBar1.Value.ToString() + "%";
        }
        /// <summary>
        /// Time consuming operations go here </br>
        /// i.e. Database operations,Reporting
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        void m_oWorker_DoWork(object sender, DoWorkEventArgs e)
        {
            // The sender is the BackgroundWorker object we need it to
            // report progress and check for cancellation.
            //NOTE : Never play with the UI thread here...
            for (int i = 0; i < 100; i++)
            {
                Thread.Sleep(100);
                // Periodically report progress to the main thread so that it can
                // update the UI.  In most cases you'll just need to send an
                // integer that will update a ProgressBar                    
                m_oWorker.ReportProgress(i);
                // Periodically check if a cancellation request is pending.
                // If the user clicks cancel the line
                // m_AsyncWorker.CancelAsync(); if ran above.  This
                // sets the CancellationPending to true.
                // You must check this flag in here and react to it.
                // We react to it by setting e.Cancel to true and leaving
                if (m_oWorker.CancellationPending)
                {
                    // Set the e.Cancel flag so that the WorkerCompleted event
                    // knows that the process was cancelled.
                    e.Cancel = true;
                    m_oWorker.ReportProgress(0);
                    return;
                }
            }
            //Report 100% completion on operation completed
            m_oWorker.ReportProgress(100);
        }
        private void btnStartAsyncOperation_Click(object sender, EventArgs e)
        {
            //Change the status of the buttons on the UI accordingly
            //The start button is disabled as soon as the background operation is started
            //The Cancel button is enabled so that the user can stop the operation 
            //at any point of time during the execution
            btnStartAsyncOperation.Enabled = false;
            btnCancel.Enabled = true;
            // Kickoff the worker thread to begin it's DoWork function.
            m_oWorker.RunWorkerAsync();
        }
        private void btnCancel_Click(object sender, EventArgs e)
        {
            if (m_oWorker.IsBusy)
            {
                // Notify the worker thread that a cancel has been requested.
                // The cancel will not actually happen until the thread in the
                // DoWork checks the m_oWorker.CancellationPending flag. 
                m_oWorker.CancelAsync();
            }
        }
    }
}
1. 开始
应用程序启动后,单击显示“开始异步操作”的按钮。UI现在会显示一个进度条,并且UI会持续更新。
 
 
2. 取消
要在中途取消并行操作,请按下取消按钮。请注意,UI线程在此期间可以执行任何额外的任务,并且不会被后台发生的密集型数据操作锁定。
 
 
3. 成功完成
 
 
成功完成并行任务后,状态栏将显示“任务已完成”。
关注点
历史
- 2010年8月4日:初始版本


