如何在不杀死 ProgressBar 的情况下等待 BackgroundWorker 完成?

How to wait for BackgroundWorker to finish without killing ProgressBar?

该应用程序的功能远不止于此,但我已通过以下示例缩小了问题范围。

注释掉bgwDone.WaitOne()后,进度条正常,取消按钮有效,但在后台进程完成前继续执行。

当应用bgwDone.WaitOne()时,ProgressForm是可见的,但没有启用,所以无法取消处理,进度条也不会刷新,而且最容易混淆的部分,Msgbox("1") 不执行。我只在后台工作人员完成后看到 Msgbox("2")。我一头雾水

Imports System.ComponentModel

Public Class Form1
    Private WithEvents bgw As BackgroundWorker
    Private Event bgwCancelled()
    Private bgwDone As New System.Threading.AutoResetEvent(False)

    'Allows ProgressForm to cancel execution
    Public Sub bgwCancelAsync()
        RaiseEvent bgwCancelled()
    End Sub

    Private Sub bgw_Cancelled_by_ProgressForm() Handles Me.bgwCancelled
        bgw.CancelAsync()
    End Sub

    Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
        Cursor = Cursors.WaitCursor
        bgw = New BackgroundWorker
        bgw.WorkerReportsProgress = True
        bgw.WorkerSupportsCancellation = True
        If bgw.IsBusy = False Then
            ProgressForm.Show()
            bgw.RunWorkerAsync(10)
        End If

        '********THIS LINE: bgwDone.WaitOne() MAKES A BIG DIFFERENCE*******
        bgwDone.WaitOne()
        MsgBox("1")
        MsgBox("2")
        Cursor = Cursors.Default
    End Sub

    'BackgroundWorker.RunWorkerAsync raises the DoWork event
    Private Sub bgw_DoWork(sender As Object, e As DoWorkEventArgs) Handles bgw.DoWork
        Dim numToDo As Integer = CInt(e.Argument)
        For n As Integer = 1 To numToDo
            If bgw.CancellationPending Then
                Exit For
            End If
            System.Threading.Thread.Sleep(200)
            bgw.ReportProgress(n * 10)
        Next
        bgwDone.Set()
    End Sub

    'ReportProgress raises the ProgressChanged event
    Private Sub bgw_ProgressChanged(sender As Object, e As ProgressChangedEventArgs) Handles bgw.ProgressChanged
        ProgressForm.UpdateProgress(e.ProgressPercentage)
    End Sub

    Private Sub bgw_RunWorkerCompleted(sender As Object,
             e As RunWorkerCompletedEventArgs) Handles bgw.RunWorkerCompleted
        ProgressForm.Close()
    End Sub

还有我的 ProgressBar 表单:

Public Class ProgressForm
    Private Sub ButtonCancel_Click(sender As Object, e As EventArgs) Handles ButtonCancel.Click
        Form1.bgwCancelAsync()
    End Sub  

    Public Sub UpdateProgress(pct As Integer)
        ProgressBar1.Value = pct
        ProgressBar1.Refresh()
    End Sub
End Class

我不确定你想要完成什么。但是看起来您的某些代码似乎试图破坏 BackGroundWorker:

的目的
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
    Cursor = Cursors.WaitCursor
    bgw = New BackgroundWorker
    ...
    If bgw.IsBusy = False Then
        ProgressForm.Show()
        bgw.RunWorkerAsync(10)
    End If

    bgwDone.WaitOne()
    MsgBox("1")
    MsgBox("2")
    Cursor = Cursors.Default
End Sub

BackgroundWorker 的目的是在另一个线程上执行一些较长的 运行 任务并使 UI 保持响应。我不确定仅“需要几秒钟”的任务是否属于长 运行 任务。

  • 鉴于此,为什么 在 BGW 运行时使用 WaitCursor?离开 UI resposive 的目的是允许用户同时做其他事情。
  • bgw.IsBusy 的测试永远不可能为真 - 您刚刚在 3 行前创建了它。再次单击该按钮,您将创建另一个 BGW。
  • 单击 中的其余代码看起来像 您希望或期望代码在 BGW 完成后在下一行继续。它不是这样工作的。
    • 如果在这些任务未完成的情况下应用程序无法继续,请禁用任何允许用户转到其他地方的内容,直到工作人员完成或:
    • 放弃 worker 并将表单置于等待模式 (Me.UseWaitCursor),直到加载内容。这不排除 ProgressBar.
  • 在应用程序将在不同时间使用不同工作人员的情况下,专门的进度表会很有意义。 StatusBar 可以包含 ProgressBar 并且更微妙(并且可能是合适的,因为它 状态元素)。

因此,修改并使用进度报告器的表单实例:

主窗体

Private WithEvents bgw As BackgroundWorker
Private frmProg As ProgressForm

Public Sub New()
    ' This call is required by the designer.
    InitializeComponent()
    ' Add any initialization after the InitializeComponent() call.
    bgw = New BackgroundWorker
End Sub

Private Sub btnLoadAll_Click(sender As Object, e As EventArgs) Handles btnLoadAll.Click

    bgw.WorkerReportsProgress = True
    bgw.WorkerSupportsCancellation = True

    If bgw.IsBusy = False Then
        ' create ProgressForm instance if needed
        If frmProg Is Nothing Then frmProg = New ProgressForm
        frmProg.Show()
        bgw.RunWorkerAsync(78)
    End If
    btnLoadAll.Enabled = False
End Sub

Private Sub bgw_DoWork(sender As Object, e As DoWorkEventArgs) Handles bgw.DoWork
    ' multiple workers can use the same event
    Dim thisWorker = DirectCast(sender, BackgroundWorker)
    Dim count = Convert.ToInt32(e.Argument)

    For n As Integer = 1 To count
        If thisWorker.CancellationPending Then
            Exit For
        End If
        ' Fake work:
        System.Threading.Thread.Sleep(50)
        ' dont assume the size of the job if
        ' there are multiple BGW or tasks
        thisWorker.ReportProgress(Convert.ToInt32((n / count) * 100))
    Next
End Sub

Private Sub bgw_ProgressChanged(sender As Object,
                        e As ProgressChangedEventArgs) Handles bgw.ProgressChanged
    frmProg.UpdateProgress(e.ProgressPercentage)
End Sub

Private Sub bgw_RunWorkerCompleted(sender As Object,
                        e As RunWorkerCompletedEventArgs) Handles bgw.RunWorkerCompleted
    If e.Error IsNot Nothing Then
        '... ToDo
    ElseIf e.Cancelled Then
        '... ToDo
    Else
        frmProg.Close()
        ' avoid 'cannot access disposed object':
        frmProg = Nothing

        Me.btnNextStep.Enabled = True
        btnLoadAll.Enabled = True
    End If

End Sub

应用程序可以自动继续,而不是启用“下一步”按钮。这取决于应用程序。