使用BackgroundWorker做多线程,SoEasy

  最近我们在学校实验室学习,老师要求用uc/os-II实现一个多线程,恰好在暑期实习的公司也做过多线程的一个东西,于是想着分享出来,同时也想把这个实例记录下来,以后用到的时候再看看。

  首先开始写这个实例之前,我想先说说为什么要用这个BackgroundWorker类。我们经常会遇到这样的情况,就是后台有大量的计算,而前台要不停的根据后台的进度调整前台的界面显示,于是微软针对这个情况实现了BackgroundWorker这个类来帮助程序员方便的解决这个问题。

  对于这个问题和这个类,微软的MSDN是这样描述的:BackgroundWorker 类允许您在单独的专用线程上运行操作。耗时的操作(如下载和数据库事务)在长时间运行时可能会导致用户界面 (UI) 似乎处于停止响应状态。如果您需要能进行响应的用户界面,而且面临与这类操作相关的长时间延迟,则可以使用 BackgroundWorker 类方便地解决问题。

  我在公司做的这个东西的目的是这样的:公司以前做了一个教务管理系统,但以前使用的是Access数据库,但现在要升级成SQLserver数据库,将以前的acess数据库中的数据导SQLserver中,于是就需要这么一个界面显示导入数据的进度。  

  以前没做过这东西,于是瞎子摸象,用VS2010用VB拖控件做,做着做着,问题不断涌现,我的想法是用表格做,再在里面添加想要的控件,这样看起来比较整齐一些,但是这样做麻烦,让同事解决这些问题时,他说你自己做一个用户控件就行了,不需要这么麻烦。哎,有时候,人的想法可以,但是实施起来麻烦,经过他这么一指点,唰唰唰,几下就做了一个用户控件,然后我开始布局这些控件的位置,他给我的建议是:进度条的多少根据后台数据库的数据的多少动态生成,这个好办,好在我学过java,做过类似的东西,不过那时候界面是定死的,控件不能随外界因素变化。好了,我感觉我的废话有点多了,开始这个实例。

  首先在这个界面中最重要的就是这个进度条,首先我们设计这个进度条组合控件,在此以截图说明:

  以上这个就是我们要的进度条组合控件,简单说明一下,Lable:显示是哪类数据;后面那个图标是动态的,说明数据正在处理中,logFile:打开日志文件;下面是进度条,后面是百分比显示。

  这个做好之后,你可以将这个控件加到其他windows窗口上,布局你自己定,但是要经过一定的计算,确定他们的相对位置。

  经过做这个东西,我领悟到一个原则,就是一个控件你的所有功能只对你自身进行操作,不要牵扯其他的东西,我做这个就是只对我做的控件进行操作,从做这个学到这个也算一个收获,以下是对这个控件的处理代码,用vb写的:

  ProgressControl.vb

  

Imports System.Drawing
Imports System.Windows.Forms
Imports System.ComponentModel
Imports System.Diagnostics
Imports System.IO


Public Class ProgressControl

    'click the logButton, then open the logFile 
    Private logFile As String

    'when the num come to maxValue, the progress is 100%
    Private maxValue As Integer = 1

    'record the finishing count
    Private finishCount As Integer = 1

    'handle of set the statePic' image
    Private Delegate Sub SetPicImageHandle(ByVal img As Image)

    'set the statePic image
    Private Sub SetPicImage(ByVal img As Image)
        If Me.statePic.InvokeRequired Then
            Dim handle As SetPicImageHandle = New SetPicImageHandle(AddressOf SetPicImage)
            Me.statePic.Invoke(handle, New Object() {img})
        Else
            Me.statePic.Image = img
        End If
    End Sub

    'handle of set the statePic' visible
    Private Delegate Sub SetPicVisibleHandle(ByVal v As Boolean)

    'set the statePic' visible
    Private Sub SetPicVisible(ByVal visible As Boolean)
        If Me.statePic.InvokeRequired Then
            Dim handle As SetPicVisibleHandle = New SetPicVisibleHandle(AddressOf SetPicVisible)
            Me.statePic.Invoke(handle, New Object() {visible})
        Else
            Me.statePic.Visible = visible
        End If
    End Sub

    'handle of set the logButton' visible
    Private Delegate Sub SetLogButtonVisibleHandle(ByVal v As Boolean)

    'set the logButton' visible
    Private Sub setLogButtonVisible(ByVal visible As Boolean)

        If Me.logButton.InvokeRequired Then
            Dim handle As SetLogButtonVisibleHandle = New SetLogButtonVisibleHandle(AddressOf setLogButtonVisible)
            Me.logButton.Invoke(handle, New Object() {visible})
        Else
            Me.logButton.Visible = visible
        End If

    End Sub

    'open the log file
    Private Sub logButton_Click(sender As System.Object, e As System.EventArgs) Handles logButton.Click

        Try
            Process.Start(logFile)
        Catch ex As FileNotFoundException
            MessageBox.Show("FileNotFoundException!")
        Catch ex As Win32Exception
            MessageBox.Show("the file isn'n exist or the file name is wrong!", "Win32Exception")
        End Try

    End Sub

    'set the statePic' image and whether show it or not
    Public Sub SetStatePic(ByVal img As Image, ByVal visible As Boolean)

        'set statePic' image
        SetPicImage(img)

        'set the statePic' visible
        SetPicVisible(visible)

    End Sub

    'set the progressbar' value and percentLabel' text
    Public Sub SetProgressValue(ByVal value As Integer)

        If value < 0 Then
            Throw New ArgumentException("the Sub SetProgressValue()' parameter is wrong!")
        Else
            Try

                Dim finishPercent As Integer = value / maxValue * 100
                Dim progressBarPercnet As Integer = progressBar.Step * finishCount

                If finishPercent = 100 Then

                    progressBar.Value = 100
                    percentLabel.Text = "100%"

                ElseIf finishPercent > 0 AndAlso finishPercent = progressBarPercnet Then

                    'change progressBar' length
                    progressBar.PerformStep()

                    'change the percentLable' text
                    percentLabel.Text = (finishCount * progressBar.Step).ToString & "%"
                    finishCount = finishCount + 1

                End If

            Catch ex As OverflowException

                MessageBox.Show(ex.Message)

            Catch e As Exception

                MessageBox.Show(e.Message)

            End Try
            
        End If

    End Sub

    'whether show the logButton or not
    Public Sub SetLogButton(ByVal visible As Boolean)

        logButton.Visible = visible

    End Sub

    'set the logFileName
    Public Sub SetLogFileName(ByVal name As String)

        logFile = name

    End Sub

    'set the itemLabel' text
    Public Sub SetItemName(ByVal name As String)

        itemLabel.Text = name

    End Sub

    'handle of set the progressBar' step
    Private Delegate Sub AppendProgressBarStepHandle(ByVal value As Integer)

    'set the progressBar' step
    Private Sub AppendProgressBarStep(ByVal value As Integer)
        If Me.progressBar.InvokeRequired Then
            Dim handle As AppendProgressBarStepHandle = New AppendProgressBarStepHandle(AddressOf AppendProgressBarStep)
            Me.progressBar.Invoke(handle, New Object() {value})
        ElseIf value <= 0 Then
            Throw New ArgumentException("the Sub AppendProgressBarStep()' parameter is wrong!")
        Else
            Me.progressBar.Step = value
        End If
    End Sub

    'handle of set the maxValue
    Private Delegate Sub AppendMaxValueHandle(ByVal value As Integer)

    'set the maxValue
    Private Sub AppendMaxValue(ByVal value As Integer)
        If Me.progressBar.InvokeRequired Then
            Dim handle As AppendMaxValueHandle = New AppendMaxValueHandle(AddressOf AppendMaxValue)
            Me.progressBar.Invoke(handle, New Object() {value})
        ElseIf value <= 0 Then
            Throw New ArgumentException("the Sub AppendMaxValue()' parameter is wrong!")
        Else
            Me.maxValue = value
        End If
    End Sub

    'set the maxValue
    Public Sub SetMaxValue(ByVal value As Integer)
        AppendMaxValue(value)
    End Sub

    Public Sub SetProgressBarStep(ByVal value As Integer)
        AppendProgressBarStep(value)
    End Sub

End Class

  

  上面的注释是用英语写的,但都是经常见的英语单词,不难理解,代码是VB.NET,vb很好理解,难的就是其中的委托,有兴趣的朋友可以看看这部分内容,感觉很有用,在这不多讲了。因为公司的这个系统是为日本做的,所以他们用的是日系windows操作系统,为了不因汉字引起问题,所以同事让我以英语加注释,很简单,没什么复杂的,我本人也是希望东西越简单越好,整那么复杂干嘛,想显示自己的高端,不,我可不这样想的。乔布斯设计苹果的原则就是简单,说这话感觉有点自恋了,呵呵...

  为了不占用太大的空间,我将图片缩小了,但是还能看到其中的内容,可以说明问题就行了。再看看怎样布局这些控件的,这部分就要认真计算一下:

  ProgressContainer.vb

  

Imports System.Drawing
Imports System.Windows.Forms
Imports System.Threading

Public Class ProgressContainer
    Implements IProgress

    'operationControl' size
    Private Const OPERATIONCONTROL_HEIGHT As Integer = 70

    'controls' space value
    Private Const SPACELENGTH As Integer = 30

    'totalStateLabel' max length
    Private Const TOTALSTATE_MAXLENGTH = 230

    'the panel' width of progressContainer
    Private Const PANEL_WIDTH = 500

    'the progressControl' count can be show in the panel
    Private Const PROGRESSCONTROL_MAXCOUNT = 5

    'the closeFlag is used to control whether the closeBox is available
    Private closeFlag As Boolean = False


    Sub New()

        ' This call is required by the designer.
        InitializeComponent()

        ' Add any initialization after the InitializeComponent() call.

        'set bottom controls' properties
        totalStateLabel.Text = "处理中..."
        totalStatePic.Image = Global.ProgressClass.My.Resources.loading
        OKButton.Enabled = False

        'set the MinimizeBox and MaximizeBox' Initial state
        Me.MinimizeBox = True
        Me.MaximizeBox = False

        'the TotalProgress form can't be closed
        closeFlag = False

    End Sub


    Private Sub TotalProgress_Unload(ByVal sender As System.Object, ByVal e As System.ComponentModel.CancelEventArgs) Handles MyBase.FormClosing

        If closeFlag = True Then

            'MessageBox.Show("make sure to close the dialog box", "CloseDialog", MessageBoxButtons.YesNo)

            If MessageBox.Show("make sure to close the dialog box", "CloseDialog", MessageBoxButtons.YesNo) = Windows.Forms.DialogResult.Yes Then
                Return
            Else
                e.Cancel = True
            End If

        Else
            e.Cancel = True
        End If

    End Sub

    Private Sub OKButton_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles OKButton.Click
        Me.Close()
    End Sub

    'the first string of Dictionary is progressControl' Name,the second is logFileName
    Public Sub CreateNewProgress(allProgress As System.Collections.Generic.Dictionary(Of String, String)) Implements IProgress.CreateNewProgress

        Dim progressControl As ProgressControl
        Dim index As Integer = 0
        Dim progressControlMaxCount As Integer = 0

        If allProgress.Count <= 0 Then
            MessageBox.Show("the Sub sendOperatonItems()' parameter has no values, please check it!")
        End If

        Try
            For Each key As String In allProgress.Keys

                progressControl = New ProgressControl()

                'set the progressControl' name
                progressControl.SetItemName(key)

                'set the logFile name
                progressControl.SetLogFileName(allProgress(key))

                'set the progressBar' value and percentLabel' text
                progressControl.percentLabel.Text = "0%"

                'set the statePic' image and show it or not
                progressControl.SetStatePic(Global.ProgressClass.My.Resources.loading, False)

                'whether show the logButton
                progressControl.SetLogButton(False)

                'set operationControl’location
                progressControl.Top = index * OPERATIONCONTROL_HEIGHT

                'add the progressControl to the progressContainer' panel
                panel.Controls.Add(progressControl)

                'set the operationControl' index to easy access it
                panel.Controls.SetChildIndex(progressControl, index)

                index = index + 1

            Next

            'set the value of most can display of progressControl
            If allProgress.Count > 5 Then
                progressControlMaxCount = PROGRESSCONTROL_MAXCOUNT
            Else
                progressControlMaxCount = allProgress.Count
            End If

            'set totalStateLabel totalStatePic and OKButton' location
            totalStateLabel.Location = New Point(2 * SPACELENGTH, progressControlMaxCount * OPERATIONCONTROL_HEIGHT + 2 * SPACELENGTH)
            totalStatePic.Location = New Point(Left + TOTALSTATE_MAXLENGTH + 4 * SPACELENGTH, progressControlMaxCount * OPERATIONCONTROL_HEIGHT + 2 * SPACELENGTH)
            OKButton.Location = New Point(Left + TOTALSTATE_MAXLENGTH + 6 * SPACELENGTH, progressControlMaxCount * OPERATIONCONTROL_HEIGHT + 2 * SPACELENGTH)

            'set the ProgressContainer' size
            Me.Size = New Size(PANEL_WIDTH + 2 * SPACELENGTH, progressControlMaxCount * OPERATIONCONTROL_HEIGHT + 5 * SPACELENGTH)

        Catch ex As InvalidOperationException
            MessageBox.Show("InvalidOperationException!")
        End Try

    End Sub

    Public Sub SetStartImg(progressNum As Integer) Implements IProgress.SetStartImg

        Dim progressControl As ProgressControl

        Try
            progressControl = panel.Controls.Item(progressNum)

            'change the statePic' image and show it
            progressControl.SetStatePic(Global.ProgressClass.My.Resources.loading, True)
        Catch ex As Exception
            MessageBox.Show("the value or type of Sub start()' parameter is wrong,please check it!")
        End Try

    End Sub

    Public Sub SetProgressValue(progressNum As Integer, progressValue As Integer) Implements IProgress.SetProgressValue

        Dim progressControl As ProgressControl

        Try
            progressControl = panel.Controls.Item(progressNum)

            'change the progressBar' value and percnetLabel' text
            progressControl.SetProgressValue(progressValue)
        Catch ex As Exception
            MessageBox.Show("the value or type of Sub sendProgressValue()' first parameter is wrong,please check it!")
        End Try

    End Sub


    Public Sub SetResultImg(progressNum As Integer, result As Boolean) Implements IProgress.SetResultImg

        Dim progressControl As ProgressControl

        Try
            progressControl = panel.Controls.Item(progressNum)

            'change the statePic to show the statePic' result
            If result = True Then
                progressControl.SetStatePic(Global.ProgressClass.My.Resources.yes, True)
            Else
                progressControl.SetStatePic(Global.ProgressClass.My.Resources.no, True)
            End If

        Catch ex As Exception
            MessageBox.Show("the value or type of Sub sendOperationResult()' first parameter is wrong,please check it!")
        End Try

    End Sub

    Public Sub FinishAllProgress() Implements IProgress.FinishAllProgress

        Dim progressControl As ProgressControl

        For i As Integer = 0 To panel.Controls.Count - 1

            progressControl = panel.Controls.Item(i)

            'to show the logButton
            progressControl.SetLogButton(True)

        Next

        'set the properties of totalStateLabel, totalStatePic and OkButton
        totalStateLabel.Text = "处理完了"
        totalStatePic.Image = Global.ProgressClass.My.Resources.yes
        OKButton.Enabled = True

        'the TotalProgress form can be closed
        closeFlag = True

    End Sub


    Public Sub SetMaxValue(progressNum As Integer, value As Integer) Implements IProgress.SetMaxValue

        Dim progressControl As ProgressControl

        progressControl = panel.Controls.Item(progressNum)

        'set the maxValue
        progressControl.SetMaxValue(value)

    End Sub

    Public Sub SetProgressBarStep(progressNum As Integer, value As Integer) Implements IProgress.SetProgressBarStep

        Dim progerssControl As ProgressControl

        progerssControl = panel.Controls.Item(progressNum)

        'set progressBar' step
        progerssControl.SetProgressBarStep(value)

    End Sub

End Class

  

  代码注释还是英文,方法名都是见名知意,在公司学的另一个就是不管给什么起名字,都要让别人知道你这里是干什么的,不能明白全部,也要略知一二。还有就是你的代码风格,要整齐有序。这个布局是这样设计的:按照你要处理的数据类型多少,自动显示多少,超过五个进度条组合控件时,只显示五个,多出的显示一个滚动条,然后拖动滚动条你就会看到多出的那些。

  好了,这个前台显示界面就算做好了,说明一下我只负责制作这个界面,那要给别人用,所以没有必要让别人知道其中的内容,只要设置一些接口即可,别人想要对你这个界面做怎样的操作,给他个接口就行。下面是这些接口:

  IProgress.vb

  

Public Interface IProgress

    'set all ' name and logfiles' name
    Sub CreateNewProgress(ByVal allProgress As Dictionary(Of String, String))

    'set the statePic' image when start one progress
    Sub SetStartImg(ByVal progressNum As Integer)

    'set the progress value of the progress item you want 
    Sub SetProgressValue(ByVal progressNum As Integer, ByVal progressValue As Integer)

    'set the result' image of one progress when it was finish
    Sub SetResultImg(ByVal progressNum As Integer, ByVal result As Boolean)

    'when you complete all Progress, you can call this sub
    Sub FinishAllProgress()

    'set the progressBar' step
    Sub SetProgressBarStep(ByVal progressNum As Integer, ByVal value As Integer)

    'set the maxValue
    Sub SetMaxValue(ByVal progressNum As Integer, ByVal value As Integer)

End Interface

  

  其实上面这些只是为我们应用BackgroundWorker做铺垫,好了,看看我们的主界面是怎样的:

  没搞错吧,就一个button,对,就只有这个,因为这个只是对我们上面做的那个界面的测试,对这个界面测试就要用到多线程。我们先来看看主界面的代码:

  MainForm.vb

  

Imports ProgressClass
Imports ProgressClass.ProgressContainer
Imports System.ComponentModel

Public Class MainForm

    Dim check As ProgressContainer
    Dim progresses As Dictionary(Of String, String)

    Private Sub Button1_Click(sender As System.Object, e As System.EventArgs) Handles Button1.Click

        worker.WorkerReportsProgress = True
        worker.WorkerSupportsCancellation = True

        'set the total form' 
        startProgress()

        'start the disposing program of Dowork' event 
        worker.RunWorkerAsync()

    End Sub


    Private Sub worker_DoWork(sender As System.Object, e As System.ComponentModel.DoWorkEventArgs) Handles worker.DoWork

        Dim workerTmp As BackgroundWorker = CType(sender, BackgroundWorker)

        'call the dispoding program of event 
        Work(workerTmp)

    End Sub

    'receive the updating progress notice
    Private Sub worker_ProgressChanged(sender As System.Object, e As System.ComponentModel.ProgressChangedEventArgs) Handles worker.ProgressChanged
        Dim index As Integer = CType(e.UserState, Integer)
        Dim progressValue As Integer = CType(e.ProgressPercentage, Integer)

        'update the progressBar' value
        check.SetProgressValue(index, progressValue)

    End Sub

    Private Sub worker_RunWorkerCompleted(sender As System.Object, e As System.ComponentModel.RunWorkerCompletedEventArgs) Handles worker.RunWorkerCompleted
        'when the Work was completed, change the totalStateLabel' text, totalStatePic' image and make the OKButton visible
        check.FinishAllProgress()
    End Sub

    Private Sub startProgress()

        check = New ProgressContainer()
        progresses = New Dictionary(Of String, String)()

        'add the items' name and log files' position
        progresses.Add("1", "e:/aaa.txt")
        progresses.Add("3", "456")

        check.CreateNewProgress(progresses)
        check.Show()
    End Sub

    Private Sub Work(ByVal workerTmp As BackgroundWorker)

        If progresses.Count > 0 Then

            For i As Integer = 0 To progresses.Count - 1

                check.SetStartImg(i)
                check.SetMaxValue(i, 1000)
                check.SetProgressBarStep(i, 9)

                For j As Integer = 0 To 1000
                    System.Threading.Thread.Sleep(10)

                    Try
                        workerTmp.ReportProgress(j, i)
                    Catch ex As InvalidOperationException
                        MessageBox.Show("InvalidOperationException")
                    End Try

                    workerTmp.ReportProgress(j, i)
                Next

                check.SetResultImg(i, True)

            Next

        End If

    End Sub

End Class

  

  下面重点介绍一下上面这部分代码,不能本末倒置嘛,要不然就文不对题了,呵呵...

  因为这个界面只有一个按钮,所以程序应该是从单击这个按钮开始的:

  Button1_Click()这个方法响应Check按钮,单击后,先做一些初始化工作,

  worker.WorkerReportsProgress :获取或设置一个值,该值指示 BackgroundWorker 能否报告进度更新。

      worker.WorkerSupportsCancellation :获取或设置一个值,该值指示 BackgroundWorker是否支持异步取消。

  startProgress()是对前台界面的初始化,基本上初始化的工作都是在ProgressContainer.vb完成的,你可以沿着调用关系去看看这些。

  worker.RunWorkerAsync()就是执行后台操作,直接运行work()方法,其中就是一些循环,我只是模拟,目的是为了证明我这个程序没错,也不能说没错,只能说是能完成基本的操作,因为我这个程序还没有经过专业人员的测试,所以不敢打这个包票。

  workerTmp.ReportProgress(j, i)就是传给前台的一些数据,这个方法引发ProgressChanged事件,第一个参数是进度条值,第二个是操作哪个进度条。

  Dim index As Integer = CType(e.UserState, Integer)

     Dim progressValue As Integer = CType(e.ProgressPercentage, Integer)

  以上这两行代码取到这个进度条的标志值和进度值。

  check.SetProgressValue(index, progressValue) :就是对这个进度条组合控件进行操作。

    RunWorkerCompleted() : 当后台操作已完成、被取消或引发异常时发生,我们这部分只是对结束后台运算时,前台所要最后做的处理,我们只调用了check.FinishAllProgress()。

  这部分算是介绍完了,看看效果:

  如果您有什么意见或者建议,请给我留言,相互学习,对我也是一个提高。

  

原文地址:https://www.cnblogs.com/dofi/p/2218727.html