Smith
Smith

Reputation: 5961

How can I use one BackgroundWorker to do different activities?

I am programming using VB.NET 2005. I am using a BackgroundWorker to load a large list or text file and process it.

Is it possible to use the same background worker to process something else, i.e. to process the text file that was loaded?
somthing like

Dim bwkMain as New BackgroundWorker()

If its possible, how do I implement it in the same form as I have already implemented for the first one?

EDIT
The question is: is it possible to use the same BackgroundWorker for another task, after it finishes one task?

Upvotes: 1

Views: 6727

Answers (3)

Alex Essilfie
Alex Essilfie

Reputation: 12613

It is possible to have a single BackgroundWorker do two or more different things. The caveat is that if you try to have the BackgroundWorker do more than one thing at a time as it will cause your code to fail.

Here is a brief overview of how to get the BackgroundWorker to do multiple activities.

  1. Check if the BackGroundWorker is not working on something.
    • If it is already working, you'll either have to wait for it to complete or cancel the current activity (which will require you adopt a different coding style in the DoWork event).
    • If it is not working, you're safe to go on to the next step.
  2. Call the BackgroundWorker's RunWorkerAsync method with an argument (or parameter) which specifies what to do.
  3. In the BackgroundWorker's DoWork event handler, check the passed argument (e.Argument) and do the desired activity.

Here is some sample code to guide you through:

Public Class Form1

    Public WithEvents bgwWorker1 As System.ComponentModel.BackgroundWorker

    Public Sub New()

        ' This call is required by the designer.
        InitializeComponent()

        ' Add any initialization after the InitializeComponent() call.
        bgwWorker1 = New System.ComponentModel.BackgroundWorker
        With bgwWorker1
            .WorkerReportsProgress = True       'we'll need to report progress
            .WorkerSupportsCancellation = True  'allows the user to stop the activity
        End With

    End Sub

    Private Sub Form1_Disposed() Handles Me.Disposed
        'you'll need to dispose the backgroundworker when the form closes.
        bgwWorker1.Dispose()
    End Sub

    Private Sub btnStart_Click() Handles btnStart.Click
        'check if the backgroundworker is doing something
        Dim waitCount = 0

        'wait 5 seconds for the background worker to be free
        Do While bgwWorker1.IsBusy AndAlso waitCount <= 5
            bgwWorker1.CancelAsync()     'tell the backgroundworker to stop
            Threading.Thread.Sleep(1000) 'wait for 1 second
            waitCount += 1
        Loop

        'ensure the worker has stopped else the code will fail
        If bgwWorker1.IsBusy Then
            MsgBox("The background worker could not be cancelled.")
        Else
            If optStep2.Checked Then
                bgwWorker1.RunWorkerAsync(2)
            ElseIf optStep3.Checked Then
                bgwWorker1.RunWorkerAsync(3)
            End If
            btnStart.Enabled = False
            btnStop.Enabled = True
        End If
    End Sub

    Private Sub btnStop_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnStop.Click
        'to stop the worker, send the cancel message
        bgwWorker1.CancelAsync()
    End Sub

    Private Sub bgwWorker1_DoWork(ByVal sender As Object, ByVal e As System.ComponentModel.DoWorkEventArgs) Handles bgwWorker1.DoWork
        'get the value to be used in performing the steps
        'in your case, you might have to convert it to a string or something 
        'and then do a Select Case on the result.
        Dim stepValue = CInt(e.Argument)

        'you cannot change the property of any control from a thread different
        'from the one that created it (the UI Thread) so this code would fail.
        'txtResults.Text &= "Starting count in steps of " & stepValue & vbCrLf

        'to perform a thread-safe activity, use the ReportProgress method like so
        bgwWorker1.ReportProgress(0, "Reported: Starting count in steps of " & stepValue & vbCrLf)

        'or invoke it through an anonymous or named method
        Me.Invoke(Sub() txtResults.Text &= "Invoked (anon): Starting count in steps of " & stepValue & vbCrLf)
        SetTextSafely("Invoked (named): Starting count in steps of " & stepValue & vbCrLf)

        For i = 0 To 1000 Step stepValue
            'Visual Studio Warns: Using the iteration variable in a lambda expression may have unexpected results.  
            '                     Instead, create a local variable within the loop and assign it the value of 
            '                     the iteration variable.
            Dim safeValue = i.ToString
            Me.Invoke(Sub() txtResults.Text &= safeValue & vbCrLf)

            'delibrately slow the thread
            Threading.Thread.Sleep(300)

            'check if there is a canellation pending
            If bgwWorker1.CancellationPending Then
                e.Cancel = True 'set this to true so we will know the activities were cancelled
                Exit Sub
            End If
        Next
    End Sub

    Private Sub SetTextSafely(ByVal text As String)
        If Me.InvokeRequired Then
            Me.Invoke(Sub() SetTextSafely(text))
        Else
            txtResults.Text &= text
        End If
    End Sub

    Private Sub bgwWorker1_ProgressChanged(ByVal sender As Object, ByVal e As System.ComponentModel.ProgressChangedEventArgs) Handles bgwWorker1.ProgressChanged
        'everything done in this event handler is on the UI thread so it is thread safe
        txtResults.Text &= e.UserState.ToString
    End Sub

    Private Sub bgwWorker1_RunWorkerCompleted(ByVal sender As Object, ByVal e As System.ComponentModel.RunWorkerCompletedEventArgs) Handles bgwWorker1.RunWorkerCompleted
        'everything done in this event handler is on the UI thread so it is thread safe
        If Not e.Cancelled Then
            txtResults.Text &= "Activities have been completed."
        Else
            txtResults.Text &= "Activities were cancelled."
        End If

        btnStart.Enabled = True
        btnStop.Enabled = False
    End Sub

    Private Sub txtResults_TextChanged() Handles txtResults.TextChanged
        'place the caret at the end of the line and then scroll to it
        'so that we always see what is happening.
        txtResults.SelectionStart = txtResults.TextLength
        txtResults.ScrollToCaret()
    End Sub
End Class

And this is the form that goes with it:
Image of Form Layout for


Also consider reading the following articles on MSDN:



Edit

The code above works in VB 10 (VS 2010) only. In order to implement the same code in other versions of VB, you'll have to write a significant amount of code as they do not support anonymous delegates.

In older versions of VB, the line

Public Sub Sample()
    Me.Invoke(Sub() txtResults.Text &= safeValue & vbCrLf)
End Sub

translates to something like this:

Public Delegate AnonymousMethodDelegate(value as String)

Public Sub AnonymousMethod(value as String)
    txtResults.Text &= value
End Sub

Public Sub Sample()
    Me.Invoke(New AnonymousMethodDelegate(AddressOf AnonymousMethod), safeValue & vbCrLf)
End Sub



Follow these steps to get the code to work in pre VB 10

Add this delegate

Delegate Sub SetTextSafelyDelegate(ByVal text As String)

And then change all the Me.Invoke(Sub() SetTextSafely(text)) to

Me.Invoke(New SetTextSafelyDelegate(AddressOf SetTextSafely), text)

Also note that anywhere that I set the text with an anonymous delegate, you'll have to rewrite the code to call the SetTextSafely method.

For instance, the line Me.Invoke(Sub() txtResults.Text &= safeValue & vbCrLf) in the for loop section of the bgwWorker_DoWork will become SetTextSafely(safeValue & vbCrLf)



If you'd like to know more about delegates, read the following articles (all from MSDN)

Upvotes: 7

Hans Passant
Hans Passant

Reputation: 941465

Very vague. It never makes sense to start another background worker and have the first one wait for it to complete. You only get a benefit from multiple threads if you can do stuff at the same time. Reading a file then processing it is a sequential operation that cannot be overlapped. Maybe you can do some of the processing concurrently, but that's unguessable from your question.

Upvotes: 1

Bala R
Bala R

Reputation: 108957

Since you cannot run your two tasks in parallel, you could do them sequentially in the same backgroundworker like this

 BackgroundWorker1.RunWorkerAsync(args)


 Private Sub BackgroundWorker1_DoWork(ByVal sender As System.Object, _
                                     ByVal e As System.ComponentModel.DoWorkEventArgs) _
                                     Handles BackgroundWorker1.DoWork

   DoTask1() ' Read files.
   DoTask2() ' Process data that was read.

End Sub


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

  'Tasks Done

End Sub

Upvotes: 2

Related Questions