65.9K
CodeProject 正在变化。 阅读更多。
Home

初学者 BackgroundWorker 类示例

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.93/5 (101投票s)

2010 年 8 月 4 日

CPOL

4分钟阅读

viewsIcon

555207

downloadIcon

21323

多线程应用程序的简单步骤

引言

本文向初级的.NET开发者介绍如何开发多线程应用程序,而无需承受多线程带来的复杂性。

背景

一个基本的Windows应用程序通常在称为UI线程的单个线程上运行。这个UI线程负责创建/绘制所有控件,并且代码在此线程上执行。因此,当您运行一个长时间运行的任务时(例如,数据密集型的数据库操作或处理数百个位图图像),UI线程将被锁定,UI应用程序将变白(请记住,UI线程负责绘制所有控件),导致您的应用程序处于“未响应”状态。

Basic.PNG

Using the Code

您需要做的是将繁重的处理转移到另一个线程上。

ParallelTask.PNG

让UI线程空闲以绘制UI。.NET为我们提供了BackgroundWorker对象来简化多线程。这个对象设计用于在另一个线程上运行一个函数,并在完成后在UI线程上调用一个事件。

步骤非常简单

  1. 创建一个BackgroundWorker对象。
  2. 告诉BackgroundWorker对象要在后台线程上运行的任务(DoWork函数)。
  3. 告诉它在工作完成后要在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,然后返回。(如果工作者认为任务太难而无法继续,它可以在没有提示的情况下设置Canceltrue并退出,而无需通过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(); 

属性

这不是一个详尽的列表,但我需要强调ArgumentResultRunWorkerAsync方法。这些是BackgroundWorker的属性,您需要了解它们才能完成任何工作。我将属性显示为您在代码中引用它们的方式。

  • DoWorkEventArgs e 用法:包含e.Argumente.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会持续更新。

Processing.PNG

2. 取消

要在中途取消并行操作,请按下取消按钮。请注意,UI线程在此期间可以执行任何额外的任务,并且不会被后台发生的密集型数据操作锁定。

Cancelled.PNG

3. 成功完成

Completed.PNG

成功完成并行任务后,状态栏将显示“任务已完成”。

关注点

历史

  • 2010年8月4日:初始版本
© . All rights reserved.