Search code examples
vb.netbackgroundworkerbackground-process

How to track multiple BackgroundworkerX.Runworkercompleted operations


I am trying to use a single handler to cover the end of multiple backgroundworker activities and cannot find a way to get the information about the specific backgroundworker using the backgroundworkercompleted event. My code to catch the event is as below:

Private Sub BGx_RunWorkerCompleted(ByVal sender As Object, ByVal e As System.ComponentModel.RunWorkerCompletedEventArgs) Handles BackgroundWorker1.RunWorkerCompleted, BackgroundWorker2.RunWorkerCompleted, BackgroundWorker3.RunWorkerCompleted, BackgroundWorker4.RunWorkerCompleted, BackgroundWorker5.RunWorkerCompleted, BackgroundWorker6.RunWorkerCompleted, BackgroundWorker7.RunWorkerCompleted, BackgroundWorker8.RunWorkerCompleted

    'Do work here based on completed Backgroundworker

    For BG = 1 To 8
        If Not DSWorkers(BG).IsBusy Then
            If DStatus(BG) = -2 Then : DStatus(BG) = -1 : End If
        End If
    Next

    Complete()
End Sub

There is nothing on the "Do Work Here" section because I do not know how to capture and have been unable to find details of the backgroundworkercompleted event id.

Please - any pointers as to how I can identify the specific completed BackgroundWorker


Solution

  • As with all event handlers, the sender parameter is a reference to the object that raised the event, so you can access the actual BackgroundWorker that has completed its work via that. If you need some data other than that, you assign it to the e.Result property in the DoWork event handler and get it back from the e.Result property in the RunWorkerCompleted event handler. e.Result works for getting data out of the DoWork event handler much as e.Argument works for getting data in.

    Check this out for some examples of using BackgroundWorker objects, including passing data out using e.Result. You might also like to checkout my own BackgroundMultiWorker class, which basically combines the functionality of multiple BackgroundWorker objects into a single BackgroundMultiWorker object. It identifies each task using a token.

    EDIT:

    Here's an example that may help with this issue and your task in general:

    Imports System.ComponentModel
    Imports System.Threading
    
    Public Class Form1
    
        Private ReadOnly resultsByWorker As New Dictionary(Of BackgroundWorker, BackgroundWorkerResult)
        Private ReadOnly rng As New Random
    
        Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
            'The NumericUpDown is used to select the index of a BackgroundWorker to cancel.
            With NumericUpDown1
                .DecimalPlaces = 0
                .Minimum = 0
                .Maximum = 9
            End With
        End Sub
    
        Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
            'Create 10 BackgroundWorkers and run them.
            For i = 1 To 10
                Dim worker As New BackgroundWorker
    
                resultsByWorker.Add(worker, New BackgroundWorkerResult)
    
                AddHandler worker.DoWork, AddressOf workers_DoWork
                AddHandler worker.RunWorkerCompleted, AddressOf workers_RunWorkerCompleted
    
                worker.WorkerSupportsCancellation = True
    
                worker.RunWorkerAsync()
            Next
        End Sub
    
        Private Sub Button2_Click(sender As Object, e As EventArgs) Handles Button2.Click
            Dim index = Convert.ToInt32(NumericUpDown1.Value)
            Dim worker = resultsByWorker.Keys.ToArray()(index)
    
            If worker.IsBusy Then
                'Cancel the BackgroundWorker at the specified index.
                worker.CancelAsync()
            End If
        End Sub
    
        Private Sub workers_DoWork(sender As Object, e As DoWorkEventArgs)
            Dim worker = DirectCast(sender, BackgroundWorker)
    
            'Do work for a random number of seconds between 10 and 20.
            Dim period = rng.Next(10, 20 + 1)
    
            For i = 0 To period
                If worker.CancellationPending Then
                    e.Cancel = True
                    Return
                End If
    
                'Simulate work.
                Thread.Sleep(1000)
            Next
    
            'The work was completed without being cancelled.
            e.Result = period
        End Sub
    
        Private Sub workers_RunWorkerCompleted(sender As Object, e As RunWorkerCompletedEventArgs)
            Dim worker = DirectCast(sender, BackgroundWorker)
            Dim result = resultsByWorker(worker)
    
            If e.Cancelled Then
                result.WasCancelled = True
            Else
                result.Result = CInt(e.Result)
            End If
    
            Dim workers = resultsByWorker.Keys.ToArray()
    
            If Not workers.Any(Function(bgw) bgw.IsBusy) Then
                'All work has completed so display the results.
    
                Dim results As New List(Of String)
    
                For i = 0 To workers.GetUpperBound(0)
                    worker = workers(i)
                    result = resultsByWorker(worker)
    
                    results.Add($"Worker {i} {If(result.WasCancelled, "was cancelled", $"completed {result.Result} iterations")}.")
                Next
    
                MessageBox.Show(String.Join(Environment.NewLine, results))
            End If
        End Sub
    End Class
    
    Public Class BackgroundWorkerResult
        Public Property WasCancelled As Boolean
        Public Property Result As Integer
    End Class
    

    Here is that example reworked to use a single instance of the BackgroundMultiWorker is linked to instead of multiple instances of the BackgroundWorker class.

    Imports System.Threading
    
    Public Class Form1
    
        Private WithEvents worker As New BackgroundMultiWorker With {.WorkerSupportsCancellation = True}
        Private ReadOnly results(9) As BackgroundWorkerResult
        Private ReadOnly rng As New Random
    
        Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
            'The NumericUpDown is used to select the index of a BackgroundWorker to cancel.
            With NumericUpDown1
                .DecimalPlaces = 0
                .Minimum = 0
                .Maximum = results.GetUpperBound(0)
            End With
        End Sub
    
        Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
            'Create 10 BackgroundWorkers and run them.
            For i = 0 To results.GetUpperBound(0)
                results(i) = New BackgroundWorkerResult
    
                worker.RunWorkerAsync(i)
            Next
        End Sub
    
        Private Sub Button2_Click(sender As Object, e As EventArgs) Handles Button2.Click
            Dim index = Convert.ToInt32(NumericUpDown1.Value)
    
            If worker.IsBusy(index) Then
                'Cancel the BackgroundWorker at the specified index.
                worker.CancelAsync(index)
            End If
        End Sub
    
        Private Sub worker_DoWork(sender As Object, e As DoWorkEventArgs) Handles worker.DoWork
            'Do work for a random number of seconds between 10 and 20.
            Dim period = rng.Next(10, 20 + 1)
    
            For i = 0 To period
                If worker.IsCancellationPending(e.Token) Then
                    e.Cancel = True
                    Return
                End If
    
                'Simulate work.
                Thread.Sleep(1000)
            Next
    
            'The work was completed without being cancelled.
            e.Result = period
        End Sub
    
        Private Sub workers_RunWorkerCompleted(sender As Object, e As RunWorkerCompletedEventArgs) Handles worker.RunWorkerCompleted
            Dim result = results(CInt(e.Token))
    
            If e.Cancelled Then
                result.WasCancelled = True
            Else
                result.Result = CInt(e.Result)
            End If
    
            If Not worker.IsBusy() Then
                'All work has completed so display the results.
    
                Dim output As New List(Of String)
    
                For i = 0 To results.GetUpperBound(0)
                    result = results(i)
    
                    output.Add($"Task {i} {If(result.WasCancelled, "was cancelled", $"completed {result.Result} iterations")}.")
                Next
    
                MessageBox.Show(String.Join(Environment.NewLine, output))
            End If
        End Sub
    End Class
    
    Public Class BackgroundWorkerResult
        Public Property WasCancelled As Boolean
        Public Property Result As Integer
    End Class