Link to home
Start Free TrialLog in
Avatar of rwallacej
rwallacej

asked on

Background Worker thread problem

I've got a progress form with ok/cancel button. If user clicks cancel the background thread stops

However, if they try to rerun process again from scratch by clicking OK to restart it there's an error

A first chance exception of type 'System.InvalidOperationException' occurred in System.dll
CalculateToolStripMenuItem_Click_1    at System.ComponentModel.BackgroundWorker.RunWorkerAsync(Object argument)
   at System.ComponentModel.BackgroundWorker.RunWorkerAsync()
   at PIEE_Energy_and_GHG_Forecaster.ProgressForm.Button1_Click(Object sender, EventArgs e) in E:\Backup VAIO 2 (USE INSTEAD OF HD)\PIEE Energy and GHG Forecaster\PIEE Energy and GHG Forecaster\ProgressForm.vb:line 58

LINE 58 of progress form does

Form1.BackgroundWorker1.RunWorkerAsync()

Help please!!!
Avatar of AlexFM
AlexFM

InvalidOperationException is thrown by RunWorkerAsync if IsBusy = true. This means, previous background operation is still running.
Show your code - how do you cancel background thread.
Simple solution can be creating new BackgroundWorker instance every time. However, thread should be stopped in any case.
Avatar of rwallacej

ASKER

code that does lots of work

            For x = 1 To MAX
                If worker.CancellationPending Then
                    Exit Sub
'probably need more in here?
                Else
                      do the calcs
                End if
   Private Sub BackgroundWorker1_DoWork(ByVal sender As System.Object, ByVal e As System.ComponentModel.DoWorkEventArgs) Handles BackgroundWorker1.DoWork
        Try
            Debug.Print("Background worker Form1")
            Dim worker As System.ComponentModel.BackgroundWorker = CType(sender, System.ComponentModel.BackgroundWorker)
            bigmethod(worker)        
       Catch ex As Exception
            utils.ExceptionLog(ex)
        End Try
    End Sub

   Private Sub BackgroundWorker1_RunWorkerCompleted(ByVal sender As System.Object, ByVal e As System.ComponentModel.RunWorkerCompletedEventArgs) Handles BackgroundWorker1.RunWorkerCompleted
        Try
            ProgressForm.Close() 'doesn't close right?
            ProgressForm.Visible = False
            If Not e.Cancelled Then
                OutputTable.ShowDialog()
            Else
                MsgBox("Cancelled calculations!")
            End If
        Catch ex As Exception
            Utils.ExceptionLog(ex)
        End Try
    End Sub


    Private Sub BackgroundWorker1_ProgressChanged(ByVal sender As System.Object, ByVal e As System.ComponentModel.ProgressChangedEventArgs) Handles BackgroundWorker1.ProgressChanged
        Try
            Debug.Print("Progress....")
            ProgressForm.ProgressBar1.Value = e.ProgressPercentage
            ProgressForm.ProgressLabel.Text = ProgressStatus
            If e.ProgressPercentage = 92 Then
                ProgressForm.ProgressLabel.Text = "Finalising Stage"
            End If
        Catch ex As Exception
            Utils.ExceptionLog(ex)
        End Try
    End Sub
So, how exectly do you cancel background thread when use clicks Cancel button?
here it is:


    Private Sub Cancel_Button_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Cancel_Button.Click
        Form1.BackgroundWorker1.CancelAsync()
        Me.DialogResult = System.Windows.Forms.DialogResult.Cancel
        Me.Close()
    End Sub
Form1.BackgroundWorker1.CancelAsync()

This is OK. You asked background worker to exit. But this doesn't mean that it exits immediately. It can continue to work.

           For x = 1 To MAX
                If worker.CancellationPending Then
                    Exit Sub              ' this is OK
                Else
                      do the calcs
                End if

Looks OK, but how much time "do the calcs" part executes? If it takes, for example, 10 seconds, background thread doesn't stop immediately. Test CancellationPending in more places to ensure fast response to cancellation request.

Let's return to CancelAsync line. It executes and continues immediately, without waiting for actual thread exit. You cal add code to wait for this:

Form1.BackgroundWorker1.CancelAsync()

while ( Form1.BackgroundWorker1.IsBusy )
    Thread.Sleep(100)
end while

Another way is to disable Start button when BackgroundWorker starts, and subscribe to BackgroundWorker.RunWorkerCompleted event. In event handler enable Start button. This ensures that Start button cannot be pressed while BackgroundWorker is busy.
Compile time says "thread is not declared"
The "do the calcs" bit can take up to 5 mins  :-(
its System.Threading.Thread.Sleep(100) - OK
If do the calcs" bit can take up to 5 mins , you must add CancellationPending tests to this code to ensure fast reaction to caclellation request.
Form1.BackgroundWorker1.IsBusy always seems to be true - so its never stopping  (I added a debug line before thread.sleep to check it was still going)

I've added       If worker.CancellationPending Then
                    Exit Sub              ' this is OK
       test in more places on the calculation

More help needed I'm afraid...thanks for help so far!
Replace "do the calcs" with something short (1-2 seconds) for testing, and you will see that IsBusy is changed to false relatively fast. Having this working, return to initial version and add more tests for CancellationPending to the code.
here's the score now...

OK button
Form1.BackgroundWorker1.RunWorkerAsync()

Cancel button
        Debug.Print("Cancelling....")
        Form1.BackgroundWorker1.CancelAsync()
        While (Form1.BackgroundWorker1.IsBusy)
            System.Threading.Thread.Sleep(100)
        End While
        Debug.Print("Reached end of cancel...closing form")                          'NEVER GETS TO HERE!

        Me.DialogResult = System.Windows.Forms.DialogResult.Cancel
        Me.Close()




   Private Sub BackgroundWorker1_DoWork(ByVal sender As System.Object, ByVal e As System.ComponentModel.DoWorkEventArgs) Handles BackgroundWorker1.DoWork
        Try
            Debug.Print("Background worker Form1")
            Dim worker As System.ComponentModel.BackgroundWorker = CType(sender, System.ComponentModel.BackgroundWorker)
            bigMethod(worker, AxSpreadsheet1) 'ExcelObjects.ExcelApp, worker, ExcelObjects.ThisWorkbook)
        Catch ex As Exception
            Debug.Print("BackgroundWorker1_DoWork " & ex.StackTrace)
            utils.ExceptionLog(ex)
        End Try
    End Sub

bigMethod
    Public Sub bigMethod(ByVal worker As System.ComponentModel.BackgroundWorker, ByRef axs As AxOWC10.AxSpreadsheet)
            For x = 1 To MAX
                If worker.CancellationPending Then
                    Exit Sub
                Else
                    Dim x As Integer

                    For x = 0 To 5000
                        If worker.CancellationPending Then
                            Exit Sub
                        Else
                            Debug.Print("col = " & x)
                        End If
                    Next
'my big calculation was here but I removed it
         end if
end for


...still never stopping when I click Cancel :-(

probably something stupid I'm doing but don't see what

Is BackgroundWorker.WorkerSupportsCancellation Property set to true?
yes, it sure is
ASKER CERTIFIED SOLUTION
Avatar of Jeff Certain
Jeff Certain
Flag of United States of America image

Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
This is my test. It contains Windows Form with two buttons: btsStart and btnStop, and BackgroundWorker1 with WorkerSupportsCancellation property = true. This is the code:

Imports System.Diagnostics
Imports System.Threading

Public Class Form1

    Private Sub btnStart_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnStart.Click
        BackgroundWorker1.RunWorkerAsync()
        btnStart.Enabled = False
    End Sub

    Private Sub BackgroundWorker1_DoWork(ByVal sender As Object, ByVal e As System.ComponentModel.DoWorkEventArgs) Handles BackgroundWorker1.DoWork
        Dim worker As System.ComponentModel.BackgroundWorker = CType(sender, System.ComponentModel.BackgroundWorker)

        Dim n As Integer = 0

        While (True)
            If worker.CancellationPending = True Then
                Trace.WriteLine("Cancelled - exit")
                Exit Sub
            End If

            Thread.Sleep(300)

            Trace.WriteLine(n.ToString)

            n = n + 1

        End While

    End Sub

    Private Sub btnStop_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnStop.Click
        If BackgroundWorker1.IsBusy = False Then
            Return
        End If

        BackgroundWorker1.CancelAsync()

        While (True)
            If BackgroundWorker1.IsBusy Then
                Exit While
            End If

            Trace.WriteLine("Waiting...")
            Thread.Sleep(100)
        End While

        Trace.WriteLine("Thread exited")
        btnStart.Enabled = True
    End Sub

End Class
SOLUTION
Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial