最近我们在学校实验室学习,老师要求用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()。
这部分算是介绍完了,看看效果:
如果您有什么意见或者建议,请给我留言,相互学习,对我也是一个提高。