I am having some difficulty dealing with stopping and then starting a background worker using VB.net when there is only a very small interval between the CancelAsync() call and the RunWorkerAsync() call. I have created an example to illustrate the problem below. The problem in this example occurs if the button is clicked once and then clicked twice in quick succession. Essentially, as I understand it, the problem is that the background worker is still iterating through the for loop (included to imitate code that takes some time to run) and has not yet had a chance to check to see if cancellation is pending.
The solution I attempted to use is to wait for the background worker to close and then start the worker again. However, this while loop iterates indefinitely - the background worker never terminates. I assume that putting the main thread to sleep also puts the background worker thread to sleep. Is this correct?
Question: what would be the recommended way of stopping a background worker and then potentially needing to start it again a fraction of a second later, when checking for a pending cancellation cannot reasonably be done any more often than it is?
Advice and comments much appreciated.
Public Class Form1
Dim WithEvents bgWorker As System.ComponentModel.BackgroundWorker = New System.ComponentModel.BackgroundWorker
Friend WithEvents startStopButton As Button = New Button
Dim workerShouldBeRunning As Boolean = False
Sub startStopButton_click() Handles startStopButton.Click
If workerShouldBeRunning Then
bgWorker.CancelAsync()
Else
While bgWorker.IsBusy
Threading.Thread.Sleep(1)
End While
bgWorker.RunWorkerAsync()
End If
workerShouldBeRunning = Not workerShouldBeRunning
End Sub
Sub bgWorker_doWork() Handles bgWorker.DoWork
While Not bgWorker.CancellationPending
Dim sum As Long
For counter = 1 To 100000
sum += counter
Next
Threading.Thread.Sleep(1000)
End While
End Sub
Private Sub Form1_Load(sender As System.Object, e As System.EventArgs) Handles MyBase.Load
startStopButton.Location = New Point(10, 100)
startStopButton.Size = New System.Drawing.Size(200, 23)
startStopButton.Text = "Click" 'Click to start or stop the background dummy process.
Me.Controls.Add(startStopButton)
bgWorker.WorkerSupportsCancellation = True
End Sub
End Class
Edit:
Using Ross Presser's advice, I have improved the example code. The solution seems a little poor, though.
Public Class Form1
Dim WithEvents bgWorker As System.ComponentModel.BackgroundWorker = New System.ComponentModel.BackgroundWorker
Friend WithEvents startStopButton As Button = New Button
Dim workerShouldBeRunning As Boolean = False
Dim startQueued As Boolean = False
Sub startStopButton_click() Handles startStopButton.Click
If workerShouldBeRunning Then
bgWorker.CancelAsync()
Else
If bgWorker.IsBusy Then
startQueued = True
Else
bgWorker.RunWorkerAsync()
End If
End If
workerShouldBeRunning = Not workerShouldBeRunning
End Sub
Sub bgWorker_doWork() Handles bgWorker.DoWork
While Not bgWorker.CancellationPending
Dim sum As Long
For counter = 1 To 100000
sum += counter
Next
Threading.Thread.Sleep(1000)
End While
End Sub
Sub bgWorkerCompleted() Handles bgWorker.RunWorkerCompleted
If startQueued Then
startQueued = False
bgWorker.RunWorkerAsync()
End If
End Sub
Private Sub Form1_Load(sender As System.Object, e As System.EventArgs) Handles MyBase.Load
startStopButton.Location = New Point(10, 100)
startStopButton.Size = New System.Drawing.Size(200, 23)
startStopButton.Text = "Click" 'Click to start or stop the background dummy process.
Me.Controls.Add(startStopButton)
bgWorker.WorkerSupportsCancellation = True
End Sub
End Class
How about this approach?...we simply replace the old backgroundworker with a new one:
Public Class Form1
Private WithEvents startStopButton As Button = New Button
Private WithEvents bgWorker As System.ComponentModel.BackgroundWorker = Nothing
Private Sub Form1_Load(sender As System.Object, e As System.EventArgs) Handles MyBase.Load
startStopButton.Location = New Point(10, 100)
startStopButton.Size = New System.Drawing.Size(200, 23)
startStopButton.Text = "Click" 'Click to start or stop the background dummy process.
Me.Controls.Add(startStopButton)
End Sub
Private Sub startStopButton_click() Handles startStopButton.Click
If IsNothing(bgWorker) Then
Static bgwCounter As Integer = 0
bgwCounter = bgwCounter + 1
bgWorker = New System.ComponentModel.BackgroundWorker
bgWorker.WorkerSupportsCancellation = True
bgWorker.RunWorkerAsync(bgwCounter)
Else
bgWorker.CancelAsync()
bgWorker = Nothing
End If
End Sub
Private Sub bgWorker_doWork(sender As Object, e As System.ComponentModel.DoWorkEventArgs) Handles bgWorker.DoWork
Dim bgwCounter As Integer = e.Argument
Debug.Print("Running #" & bgwCounter)
Dim bgw As System.ComponentModel.BackgroundWorker = sender
While Not bgw.CancellationPending
Dim sum As Long
For counter As Integer = 1 To 100000
sum += counter
Next
Threading.Thread.Sleep(1000)
End While
Debug.Print("Cancelled #" & bgwCounter)
End Sub
End Class