初学者 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日:初始版本