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

用自有类封装 BackgroundWorker,以简化多线程应用程序的创建

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.79/5 (8投票s)

2015 年 8 月 6 日

CPOL

4分钟阅读

viewsIcon

17912

downloadIcon

537

本文介绍了一个应用程序,该应用程序在单独的线程上对整数数组执行几种不同的排序算法,并以图形方式可视化它们。应用程序主线程使用封装 BackgroundWorker 的特殊 C# 类与排序线程进行交互。

引言

该应用程序有三个有用的原因。首先,它提供了同时观察多个排序过程执行的机会。其次,它展示了我们如何通过自定义类来封装 BackgroundWorker,以自动化其设置。第三,它是 MVC 模式的示例实现。

背景

很久以前,我曾使用 Borland Delphi 教程中的一个很棒的训练示例 - thrddemo - 来教我的学生如何使用 VCL TThread 对象创建多线程应用程序。该示例的主要优点是可以使用简单的 TThread 派生类在单独的线程中启动不同的排序算法。现在我教 C#,需要一些类似 .Net 框架的东西。决定使用 BackgroundWorker 实例。但是,直接使用它们会导致大量重复代码。然后,我在 codeproject.com 上看到了 Hieuuk 在文章“Working with BackgroundWorker & Creating Your Own Loading Class”中提供的一个有用的技巧,并决定创建一个自己的类 - BackgroundSorter - 来处理所有繁重的工作。现在我想描述我的工作成果。

应用程序要求

我们的目标是构建一个多线程应用程序。它的主线程将维护 GUI 并与四个后台线程进行交互。每个线程将通过已知的排序算法对整数数组进行排序,并在应用程序窗口中显示数组元素的每次交换。数组将显示在窗口中,作为一段段的列。每个段的长度将与数组中相应元素的长度成比例。应用程序用户将能够在排序开始前生成新数组,启动排序线程,并提前终止任何线程。

我们将使用 BackgroundWorker 实例来控制排序线程。称其为 bw。让我们回顾一下在使用它之前需要进行哪些设置。首先,我们必须定义一个排序方法并将其分配给 bw.DoWork 事件。然后,我们必须将属性 bw.WorkerSupportsCancellationbw.WorkerReportsProgress 设置为 true,并为 bw.ProgressChanged 事件分配一个处理程序方法。此事件将告知我们数组元素的交换情况。最后,我们需要为 bw.RunWorkerCompleted 事件设置一个处理程序。

自然,排序方法必须以特殊方式安排,以便它可以与 bw 交互。它必须检查 bw.CancellationPending 属性以按需中断执行,并且每次进行值交换时,它还应该调用 bw.ReportProgress()

BackgroundSorter 的设计

我们的新类将充当 BackgroundWorker 和外部代码之间的中介。其任务如下:

  1. BackgroundWorker 与整数数组和排序方法连接起来。
  2. BackgroundWorker 的事件提供处理程序。
  3. 提供一个合适的接口来控制排序线程。
  4. 通知外部代码排序线程中的重要事件。

因此,BackgroundSorter 的数据成员可能如下所示:

public class BackgroundSorter
{
    private BackgroundWorker worker;
    private int[] arrayToSort;
    private SortMethod sortMethod;
    // Events affecting the view:
    // - exchange of two array elements
    public event SortingExchangeEventHandler SortingExchange;
    // - completion of the sorting process
    public event SortingCompleteEventHandler SortingComplete;
    // ...

我们将使用具有明确名称的方法来控制排序线程(而不是 BackgroundWorker 的通用名称方法)。

    // access to the backgroundWorker interface
    public void Execute()
    {
        worker.RunWorkerAsync(arrayToSort);
    }
    public void Stop()
    {
        worker.CancelAsync();
    }

构造函数将完全设置 worker

    public BackgroundSorter(int[] array, SortMethod theMethod)
    {
        worker = new BackgroundWorker();
        worker.WorkerReportsProgress = true;
        worker.WorkerSupportsCancellation = true;
    // Set-up required methods to interact with the backgroundWorker:
    // - main long term work
        worker.DoWork += worker_DoWork;
    // - action after main work completion
        worker.RunWorkerCompleted += worker_RunWorkerCompleted;
    // - displaying of main work progress
        worker.ProgressChanged += worker_ProgressChanged;

        this.arrayToSort = array;
        this.sortMethod = theMethod;
    }

其中 worker_DoWork、worker_RunWorkerCompleted 和 worker_ProgressChanged 是 BackgroundSorter 的方法。第一个将调用排序方法,第二个将检查 worker 是否在没有错误的情况下完成并通知应用程序排序完成,第三个将把“进度更改”消息转换为整数交换的术语。

    // The backgroundWorker events handlers:
    // - long term execution - the array sorting
    private void worker_DoWork(object sender, DoWorkEventArgs e)
    {
        if (sortMethod != null && arrayToSort != null)
        {
            sortMethod((int[])e.Argument, sender as BackgroundWorker, e);
        }
        else throw new NullReferenceException("Trying to use null array or null sorting method");
    }
    // - handler of completion of the sorting process
    private void worker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
    {
        if (e.Error != null) MessageBox.Show(e.Error.Message);
        OnSortingComplete(e.Cancelled);
    }
    // - handler of sorting progress reports indexes of exchanged elements
    private void worker_ProgressChanged(object sender, ProgressChangedEventArgs e)
    {
        // the elements indexes are packed in the property ProgressPercentage
        int i = e.ProgressPercentage / 1000;
        int j = e.ProgressPercentage % 1000;
        OnSortingExchange(i, j);
    }

请注意,ProgressChanged 事件的参数可以报告一个整数,但我们需要至少两个:需要交换的数组元素的索引。克服这个矛盾的一个简单方法是将索引打包成一个数字,如下所示:number=firstIndex\*1000+secondIndex。这适用于任何不超过一千个元素的数组。

您可以在下载文件中找到 BackgroundSorter 的完整版本。

应用程序架构

简要介绍整个应用程序。它根据 MVC 模式构建。

SortModel 类充当模型。它包含四个相同的数组并提供初始化它们的方​​法。VisualForm 类描述了应用程序的主窗口,并使用用户控件 ArraySortingView 的四个实例来显示排序过程。ArraySortingView 类的考虑范围超出了本文。

SortController 类封装了 BackgroundSorter 的四个实例,并使用 SortMethodsProvider 类的静态方法。每个这样的方法都设计用于对数组进行排序并同时与后台工作线程进行交互。让我们看一个。交互部分用粗体标记。

// Bubble sort algorithm (simple exchange method)
public static void BubbleSortInBackground(int[] arrayToSort, BackgroundWorker worker, DoWorkEventArgs e)
{
    for (int i = arrayToSort.Length - 1; i > 0; --i)
        for (int j = 0; j < i; ++j)
        {
            // Cancel request checking
            if (worker.CancellationPending)
            {
                e.Cancel = true;
                return;
            }
            // Looking for pairs of neighboring elements situated in wrong order
            if (arrayToSort[j] > arrayToSort[j + 1])
            {
                // Exchange vizualization in the main thread
                worker.ReportProgress(j * 1000 + (j + 1));
                System.Threading.Thread.Sleep(delay);
                // The exchange: to correct a "wrong pair" of elements
                int t = arrayToSort[j];
                arrayToSort[j] = arrayToSort[j + 1];
                arrayToSort[j + 1] = t;
            }
        }
}

应用程序类图如下所示。应用程序的所有代码都在下载文件中。

应用程序“Sorting in threads”的类图

让我们看一个正在运行的应用程序的截图。四个排序线程中有两个已完成,另外两个仍在工作。

运行应用程序“Sorting in threads”

© . All rights reserved.