How many of these might there be? You should be careful of creating a 1:1 relationship between items in a collection and threads. Another coder might come along and expand this collection more than you planned.
For things like this I usually like to use just the one thread, and a queue - so the events just put the work that needs to be done in a ConcurrentQueue, and the thread starts if not running, and churns through the queued work until it's out of things to do, and dies. The next time more work comes in the event will check if the thread is running and start it.
It's cheaper because if there's a lot going on, you run just the one thread instead of stopping and starting a lot of them, or if there's very little going on, the one thread is almost never running.
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.
- 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.
- Call the BackgroundWorker's
RunWorkerAsync
method with an argument (or parameter) which specifies what to do.
- 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:
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)
Best Answer
You could store ready-to-process files in another queue (like a Queue< string> ) and have the BgWorker continuously poll that Queue. You might get better performance too, less idle time. You will have to protect the Queue (with Monitor) and have the BgWorker use Monitor.Wait when the Queue is empty.
To get an idea, look for Marc Gravell's answer on this question.