vb.net更新进度条多线程

时间:2014-11-08 04:35:24

标签: vb.net multithreading

长期读者,第一次海报。通常我能找到答案并使其有效。不是这次.....我在VS2013中使用VB.NET。我正在尝试使用辅助线程中的工作更新进度条。容易吗?不,我必须让它变得更复杂。进度条( ToolStripProgressBar1 )位于主窗体( frmMain )上,即项目的MDI。辅助表单( frmShipping )有一个按钮,用于启动第二个线程以在类中执行某些COMM端口通信( cApex )。我可以从主UI线程( frmShipping 按钮)获取进度条以更新 frmMain

这是来自 frmShiping 按钮的代码和多线程程序:

 Private Sub btnreadScanner_Click(sender As Object, e As EventArgs) Handles btnreadScanner.Click

    Dim thrReadScanner As New System.Threading.Thread(AddressOf ReadScanner)
    thrReadScanner.IsBackground = True
    thrReadScanner.Start()

End Sub

    Private Sub ReadScanner()

    Dim strRowCount As String
    ShipmentMsg(2)
    strRowCount = objShipping.RecordsExisit.ToString()

    Try
        objApex.ImmediateMode()
        If objApex.FileDownload = False Then
            Throw New Exception(Err.Description)
        End If
    Catch ex As Exception
        ShipmentMsg(1)
        MessageBox.Show("No Data downloaded from Scanner.  Try Again.  Error#: " & Err.Number & " : " & Err.Description)
        Exit Sub
    End Try

    RecordCount()
    DataGridUpdate()
    btnProcessShipment.Enabled = True
    ShipmentMsg(5)
    ScanErrors()

End Sub

这一切都很棒。正如所料。类 cApex 中对 objApex.FileDownload 的调用是更新进度条的位置(实际上是从FileDownload调用的另一个函数)。所以这里是代码。

Try
        GetHeaderRecord()
        If Count <> 0 Then intTicks = Math.Round((100 / Count), 1)
        For intcount As Integer = 1 To Count
            Dim intLength As Integer = Length
            Do While intLength > 0
                literal = Chr(_serialPort.ReadChar.ToString)
                If literal = ">" Then Exit Do
                strRecord = strRecord & literal
                intLength = intLength - 1
            Loop
            REF = strRecord.Substring(0, 16).TrimEnd
            SKID = strRecord.Substring(16, 16).TrimEnd
            REEL_BC = strRecord.Substring(32, 15).TrimEnd
            ScanDate = strRecord.Substring(47, 8).TrimEnd
            ScanDate = DateTime.ParseExact(ScanDate, "yyyyMMdd", Nothing).ToString("MM/dd/yyyy")
            ScanTime = strRecord.Substring(55, 6).TrimEnd
            ScanTime = DateTime.ParseExact(ScanTime, "HHmmss", Nothing).ToString("HH:mm:ss")
            strRecordTotal = strRecordTotal & strRecord & CRLF
            Dim strSQL As String
            strSQL = "INSERT INTO tblScanData (PONo,Barcode,SkidNo,ScanDate,ScanTime) " & _
            "VALUES (" & _
           Chr(39) & REF & Chr(39) & _
           "," & Chr(39) & REEL_BC & Chr(39) & _
           "," & Chr(39) & SKID & Chr(39) & _
           "," & Chr(39) & ScanDate & Chr(39) & _
           "," & Chr(39) & ScanTime & Chr(39) & ")"
            objData.Executecommand(strSQL)
            strRecord = ""
        Next

最后这就是我调用进度条更新的方式。

Dim f As frmMain = frmMain
System.Threading.Thread.Sleep(100)
DirectCast(f, frmMain).ToolStripProgressBar1.PerformStep()

我真的需要将PerformStep放在For循环中。每次循环都会使进度条步进使条形相当准确所需的步数百分比(在循环之前由数学代码完成)。我还在 frmMain 上设置了进度条控件的属性。那么,我是疯了,还是有办法实现这一目标?我尝试过使用代理人; Me.Invoke(New MethodInvoker(AddressOf pbStep))使代码跨线程安全。我没有得到关于跨线程调用的错误,但进度条也没有更新。对不起,这是一个很长的,但我迷路了,我的多动症不会让我废弃这个想法。

根据要求编辑:

 Public Sub pbStep()

    Dim f As frmMain = frmMain
    If Me.InvokeRequired Then
        Me.Invoke(New MethodInvoker(AddressOf pbStep))
    Else
        DirectCast(f, frmMain).ToolStripProgressBar1.PerformStep()
        System.Threading.Thread.Sleep(100)
    End If

End Sub

2 个答案:

答案 0 :(得分:2)

这两个回复都帮助我找到了我需要的正确答案。 James提供的代码是一个很好的起点,Hans有几篇文章解释了BackgroundWorker。我想分享一下我提出的“答案”。我并不是说这是最好的方法,而且我确定我违反了一些共同逻辑规则。此外,许多代码来自MSDN示例和James的代码。

让我从我调用bgw,frmShipping的表单开始。我添加了这段代码:

Dim WithEvents bgw1 As New System.ComponentModel.BackgroundWorker

Private Sub bgw1_RunWorkerCompleted(ByVal sender As Object, ByVal e As System.ComponentModel.RunWorkerCompletedEventArgs) _
    Handles bgw1.RunWorkerCompleted

    If e.Error IsNot Nothing Then
        MessageBox.Show("Error: " & e.Error.Message)
    ElseIf e.Cancelled Then
        MessageBox.Show("Process Canceled.")
    Else
        MessageBox.Show("Finished Process.")
    End If

End Sub

Private Sub bgw1_ProgressChanged(ByVal sender As Object, ByVal e As System.ComponentModel.ProgressChangedEventArgs) _
    Handles bgw1.ProgressChanged
    DirectCast(Me.MdiParent, frmMain).ToolStripProgressBar1.Maximum = 1960
    DirectCast(Me.MdiParent, frmMain).ToolStripProgressBar1.Step = 2

    Dim state As cApex.CurrentState =
        CType(e.UserState, cApex.CurrentState)
    DirectCast(Me.MdiParent, frmMain).txtCount.Text = state.LinesCounted.ToString
    DirectCast(Me.MdiParent, frmMain).txtPercent.Text = e.ProgressPercentage.ToString
    DirectCast(Me.MdiParent, frmMain).ToolStripProgressBar1.PerformStep()

End Sub
Private Sub bgw1_DoWork(ByVal sender As Object, ByVal e As System.ComponentModel.DoWorkEventArgs) _
    Handles bgw1.DoWork

    Dim worker As System.ComponentModel.BackgroundWorker
    worker = CType(sender, System.ComponentModel.BackgroundWorker)

    Dim objApex As cApex = CType(e.Argument, cApex)
    objApex.CountLines(worker, e)

End Sub

Sub StartThread()

    Me.txtCount.Text = "0"
    Dim objApex As New cApex
    bgw1.WorkerReportsProgress = True
    bgw1.RunWorkerAsync(objApex)

End Sub

接下来,我将以下代码添加到我的cApex类中。

Public Class CurrentState
    Public LinesCounted
End Class

Private LinesCounted As Integer = 0


Public Sub CountLines(ByVal worker As System.ComponentModel.BackgroundWorker, _
                       ByVal e As System.ComponentModel.DoWorkEventArgs)
    Dim state As New CurrentState
    Dim line = ""
    Dim elaspedTime = 20
    Dim lastReportDateTime = Now
    Dim lineCount = File.ReadAllLines(My.Settings.strGenFilePath).Length
    Dim percent = Math.Round(100 / lineCount, 2)
    Dim totaldone As Double = 2

    Using myStream As New StreamReader(My.Settings.strGenFilePath)

        Do While Not myStream.EndOfStream
            If worker.CancellationPending Then
                e.Cancel = True
                Exit Do
            Else
                line = myStream.ReadLine
                LinesCounted += 1
                totaldone += percent

               If Now > lastReportDateTime.AddMilliseconds(elaspedTime) Then
                    state.LinesCounted = LinesCounted
                    worker.ReportProgress(totaldone, state)
                    lastReportDateTime = Now
                End If
                System.Threading.Thread.Sleep(2)
            End If
        Loop

        state.LinesCounted = LinesCounted
        worker.ReportProgress(totaldone, state)

    End Using

End Sub

我还在我的主窗体中添加了几个文本框,以显示正在读取的文件中的当前行数和总体进度百分比。然后在我的按钮的Click事件中,我只是调用{ {1}}。它不是100%准确,但足够接近,可以让用户非常了解流程的位置。我还有一些工作要做,将它添加到“ReadScanner”函数中,我最初想要使用进度条。但是这个功能是我在扫描仪上执行的两个中的较长时间,通过COMM端口写入近2,000行代码。我对结果感到满意。

谢谢大家的帮助!

P.S。我现在还添加了变量来设置StartThread()pbar.Maximum,因为如果扫描程序文件发生了变化,这些变量就会发生变化。

答案 1 :(得分:0)

后台工作人员可用于此目的。只需将其与代表一起使用即可。所有线程工作都在worker的DoWork事件中完成。随着进展,在DoWork事件中报告进度。这反过来触发worker类的ProgressedChanged事件,该事件与进度条位于同一个线程上。 DoWork完成并超出范围后,将触发RunWorkerCompleted事件。这可以用来告知用户任务完成等等。这是一个工作解决方案,我把它放在一起。只需将其粘贴在空表格后面即可运行。

Imports System.Windows.Forms
Imports System.ComponentModel
Imports System.Threading

Public Class Form1
    Private _progressBar As ProgressBar
    Private _worker As BackgroundWorker

    Sub New()
        ' This call is required by the designer.
        InitializeComponent()
        Initialize()
        BindComponent()
    End Sub

    Private Sub Initialize()
        _progressBar = New ProgressBar()
        _progressBar.Dock = DockStyle.Fill

        _worker = New BackgroundWorker()
        _worker.WorkerReportsProgress = True
        _worker.WorkerSupportsCancellation = True

        Me.Controls.Add(_progressBar)
    End Sub

    Private Sub BindComponent()
        AddHandler _worker.ProgressChanged, AddressOf _worker_ProgressChanged
        AddHandler _worker.RunWorkerCompleted, AddressOf _worker_RunWorkerCompleted
        AddHandler _worker.DoWork, AddressOf _worker_DoWork
        AddHandler Me.Load, AddressOf Form1_Load
    End Sub

    Private Sub Form1_Load()
        _worker.RunWorkerAsync()
    End Sub

    Private Sub _worker_ProgressChanged(ByVal o As Object, ByVal e As ProgressChangedEventArgs)
        _progressBar.Increment(e.ProgressPercentage)
    End Sub

    Private Sub _worker_RunWorkerCompleted(ByVal o As Object, ByVal e As RunWorkerCompletedEventArgs)

    End Sub

    Private Sub _worker_DoWork(ByVal o As Object, ByVal e As DoWorkEventArgs)
        Dim worker = DirectCast(o, BackgroundWorker)

        Dim value = 10000

        SetProgressMaximum(value)

        For x As Integer = 0 To value
            Thread.Sleep(100)
            worker.ReportProgress(x)
        Next
    End Sub

    Private Sub SetProgressMaximum(ByVal max As Integer)
        If _progressBar.InvokeRequired Then
            _progressBar.Invoke(Sub() SetProgressMaximum(max))
        Else
            _progressBar.Maximum = max
        End If
    End Sub

End Class