Search code examples
vb.netbackgroundworker

Forwarding the min and max values of BackgroundWorker to use the function correctly


I have been struggling for several days to find a solution on how to properly incorporate BackgroundWorker into my Feature and with that I have the ability to properly display the process development, process stop, report.

this is my code

 Private Sub Frm_ImportLeumobileGK_FormClosing(sender As Object, e As Windows.Forms.FormClosingEventArgs) Handles MyBase.FormClosing
        Me.BackgroundWorker1.CancelAsync()
        Me.Timer1.Enabled = False
        Me.DialogResult = DialogResult.Cancel
        e.Cancel = True
    End Sub

     Private Sub Btn_OK_Click(sender As Object, e As EventArgs) Handles btn_OK.Click
        ListView1.Items.Clear()
        resetCounter()
        BackgroundWorker1.RunWorkerAsync()
    End Sub


      Private Sub BackgroundWorker1_DoWork(sender As Object, e As DoWorkEventArgs) Handles BackgroundWorker1.DoWork
        Dim worker As BackgroundWorker = TryCast(sender, BackgroundWorker)
        TestPut_All_CDRs_To_FakturaPos(worker, e,
                         MasterMandantConnectionString:=MasterMandantConnectionString,
                         StartDate:=dtp_Start.Value,
                         EndDate:=dtp_End.Value,
                         min_Nr:=tb_Min_Nr.Value,
                         max_Nr:=tb_Max_Nr.Value)
    End Sub

      Sub TestPut_All_CDRs_To_FakturaPos(worker As BackgroundWorker, e As DoWorkEventArgs, MasterMandantConnectionString As String, StartDate As Date, EndDate As Date, min_Nr As Integer, max_Nr As Integer)
        Dim n As Integer = 0
        Dim MobCdrs As List(Of String)
        ProgressBar1.Maximum = (max_Nr - min_Nr)

        For Mob_Nr = min_Nr To max_Nr
            n += 1
            If worker.CancellationPending Then
                e.Cancel = True
            Else
                MobCdrs = TestPut_Mob_CDRs_To_FakturaPos(MasterMandantConnectionString:=MasterMandantConnectionString,
                                   StartDate:=StartDate,
                                   EndDate:=EndDate,
                                   Mob_Nr:=Mob_Nr)
                For Each currentError In MobCdrs
                    If (currentError <> "") Then
                        ListView1.Items.Add(currentError)
                    End If
                Next
                If n > ProgressBar1.Maximum Then
                    n = ProgressBar1.Maximum
                End If
                ProgressBar1.Value = n
            End If
        Next
        ListView1.Items.Insert(0, getImportInfo(), 0)
        labelInfo.Text = "Test successfully completed."
    End Sub
  Private Sub BackgroundWorker1_ProgressChanged(sender As Object, e As ProgressChangedEventArgs) Handles BackgroundWorker1.ProgressChanged
        ProgressBar1.Value = e.ProgressPercentage
    End Sub

    Private Sub Frm_FormClosing(sender As Object, e As Windows.Forms.FormClosingEventArgs) Handles MyBase.FormClosing 
If BackgroundWorker1 IsNot Nothing Then
        Me.BackgroundWorker1.CancelAsync()
        Me.Close()
    End If

End Sub

Using Documentation, I tried to apply BackgroundWorker to my "TestPut_All_CDRs_To_FakturaPos" function, unfortunately failed because I get on ProgressBar1.Maximum error = ***"System.InvalidOperationException: "Invalid cross threading operation: The ProgressBar1 control was accessed from a thread other than the thread it was created for."***Please suggest, where am I making an exception?


Solution

  • What you need to do is separate the user interface parts (like ListViews, MessageBoxes, etc.) from the backgroundworker.

    • The way to get the data into the BGW is to pass an object into its .Argument.
    • To get data out of it while it is running, use the ReportProgress event and pass whatever you want in the .UserState object.
    • To get data from it when it has finished, use the .Result property.

    We won't be using any result in this case, but I will set it up as a Boolean in case you want to modify it. So, let's create classes to get the data in and out...

    Private Class BgwArgs
        Property StartDate As DateTime
        Property EndDate As DateTime
    
        Property MinNr As Integer
        Property MaxNr As Integer
    
        Property ConnStr As String
    
    End Class
    
    Private Class ProgressReportData
        Property ErrorMessages As List(Of String)
    
    End Class
    

    The initial setup for the BGW is like this:

    Private Sub Btn_OK_Click(sender As Object, e As EventArgs) Handles Btn_OK.Click
        ListView1.Items.Clear()
        ResetCounter()
    
        Dim args As New BgwArgs With {.StartDate = dtp_Start.Value,
                                      .EndDate = dtp_End.Value,
                                      .MinNr = CInt(tb_Min_Nr.Value),
                                      .MaxNr = CInt(tb_Max_Nr.Value),
                                      .ConnStr = "your connection string"}
    
        ProgressBar1.Minimum = 0
        ProgressBar1.Maximum = 100
    
        BackgroundWorker1.WorkerReportsProgress = True
        BackgroundWorker1.WorkerSupportsCancellation = True
    
        BackgroundWorker1.RunWorkerAsync(args)
    
    End Sub
    

    and then all the parts:

    Private Sub BackgroundWorker1_DoWork(sender As Object, e As DoWorkEventArgs) Handles BackgroundWorker1.DoWork
        Dim worker As BackgroundWorker = TryCast(sender, BackgroundWorker)
    
        e.Result = TestPut_All_CDRs_To_FakturaPos(worker, e)
    
    End Sub
    
    Private Function TestPut_All_CDRs_To_FakturaPos(worker As BackgroundWorker, e As DoWorkEventArgs) As Boolean
    
        Dim importedInfo As New List(Of String)
    
        Dim args = CType(e.Argument, BgwArgs)
        Dim masterMandantConnectionString = args.ConnStr
        Dim startDate = args.StartDate
        Dim endDate = args.EndDate
        Dim min_Nr = args.MinNr
        Dim max_Nr = args.MaxNr
    
        Dim n As Integer = 0
        Dim totalMobs = max_Nr - min_Nr + 1
    
        For mob_Nr = min_Nr To max_Nr
            n += 1
            If worker.CancellationPending Then
                e.Cancel = True
            Else
                Dim mobCdrs = TestPut_Mob_CDRs_To_FakturaPos(MasterMandantConnectionString:=masterMandantConnectionString,
                                   StartDate:=startDate,
                                   EndDate:=endDate,
                                   Mob_Nr:=mob_Nr)
    
                Dim pct = n * 100 \ totalMobs
                Dim progReport As New ProgressReportData With {.ErrorMessages = mobCdrs.Where(Function(m) Not String.IsNullOrEmpty(m)).ToList()}
    
                worker.ReportProgress(pct, progReport)
    
            End If
        Next
    
        Return True
    
    End Function
    
    Private Sub backgroundWorker1_ProgressChanged(ByVal sender As Object, ByVal e As ProgressChangedEventArgs) Handles BackgroundWorker1.ProgressChanged
        Dim progData = CType(e.UserState, ProgressReportData)
        ProgressBar1.Value = e.ProgressPercentage
        If progData.ErrorMessages IsNot Nothing Then
            For Each m In progData.ErrorMessages
                ListView1.Items.Add(m)
            Next
        End If
    
    End Sub
    
    Private Sub backgroundWorker1_RunWorkerCompleted(ByVal sender As Object, ByVal e As RunWorkerCompletedEventArgs) Handles BackgroundWorker1.RunWorkerCompleted
        If (e.Error IsNot Nothing) Then
            ProgressBar1.ForeColor = Color.Red
            MessageBox.Show(e.Error.Message, "Exception", MessageBoxButtons.OK, MessageBoxIcon.Error)
    
        ElseIf e.Cancelled Then
            ' Next, handle the case where the user cancelled the operation.
            ' Note that due to a race condition in the DoWork event handler, the Cancelled flag may not have been set, even though CancelAsync was called.
            ProgressBar1.ForeColor = Color.HotPink
    
        Else
            ProgressBar1.ForeColor = Color.LawnGreen
            ListView1.Items.Insert(0, GetImportInfo(), 0)
            labelInfo.Text = "Test successfully completed."
    
            ' We could use e.Result here if something useful was returned in it.
            ' Dim flag = CType(e.Result, Boolean)
    
        End If
    
    End Sub
    

    The progress percentage is calculated in the loop, as that's an easy way to get it done.

    I couldn't test it, but hopefully there's enough there for you to get working code.

    (I use Option Infer On and Option Strict On.)