BackgroundWorker.ReportProgress exception if run in another module - vb.net

My BackgroundWorker works perfectly in my main form frmMain. But when I run the ReportProgress method in another module, I get exception "This BackgroundWorker states that it doesn't report progress. Modify WorkerReportsProgress to state that it does report progress." This IS set to report progress; this works fine when run the same way in the main module.
Basically, from a module called by my BackgroundWorker, I want to show progress on my main form.
How can I fix this? The only idea I have is to move the code from the module into my main form, but this seems a backward step, which would involve extra work. Am hoping there are easier ways!
Calling code in class frmMain:
Friend WithEvents BackgroundWorker As New System.ComponentModel.BackgroundWorker
Private Sub btnTest_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnTest.Click
' Specify that we do NOT want the background operation to allow cancellation
BackgroundWorker.WorkerSupportsCancellation = False
' Specify that we want the background operation to report progress.
BackgroundWorker.WorkerReportsProgress = True
' Start running the background operation by calling the RunWorkerAsync method.
BackgroundWorker.RunWorkerAsync()
End Sub
Private Sub BackgroundWorker_DoWork(ByVal sender As Object, _
ByVal e As System.ComponentModel.DoWorkEventArgs) Handles BackgroundWorker.DoWork
Dim result As Boolean
result = MyTest()
End Sub
Private Sub BackgroundWorker_ProgressChanged(ByVal sender As Object, ByVal e As System.ComponentModel.ProgressChangedEventArgs) Handles BackgroundWorker.ProgressChanged
Me.Text = e.ProgressPercentage.ToString() & "%"
sspStatus.Text = e.UserState.ToString
End Sub
Private Sub BackgroundWorker_RunWorkerCompleted(ByVal sender As Object, _
ByVal e As System.ComponentModel.RunWorkerCompletedEventArgs) _
Handles BackgroundWorker.RunWorkerCompleted
If e.Cancelled = True Then
' The background operation was cancelled
Me.Text = "Cancelled!"
ElseIf e.Error IsNot Nothing Then
' The background operation encountered an error
Me.Text = "Error: " & e.Error.Message
Else
' The background operation completed successfully
Me.text = "Done!"
End If
End Sub
Code which generates the exception in separate module Invoices:
Public Function MyTest() As Boolean
frmMain.BackgroundWorker.ReportProgress(0)
End Function
Am using VB.NET in VS 2010, with .NET 3.5.

Try to set it up as
Public Function MyTest(worker as BackgroundWorker) As Boolean
worker.ReportProgress(0)
End Function
to make sure you are talking to the right worker instance.
(And aside: avoid using classnames for instance fields).

Related

Interact with form while working in the background

The goal is that the form should still be clickable while the code runs (mainly to be able to break the code) AND keeps getting updated by the running code at the same time. First part works, not the second part.
Public Class FormMain
Public PleaseStopAll As Boolean
Private Sub BreakCode_Click(sender As Object, e As EventArgs) Handles BreakCode.Click
PleaseStopAll = True
End Sub
Private Sub Testeur_Click(sender As Object, e As EventArgs) Handles Testeur.Click
PleaseStopAll = False
ThreadPool.QueueUserWorkItem(AddressOf LaunchTask, 0)
End Sub
Sub LaunchTask(o As Object)
TheTask(Me)
End Sub
Public Sub ShowSomeMessage(msg As String
Me.Displayer.text = msg
End Sub
End Class
Module TaskDoer
Sub TheTask(MyMenu As FormMain)
For i As Integer = 0 To 42
whatever() 'some tasks that cannot be interrupted safely
MyMenu.ShowSomeMessage("task #" & CStr(i) & " over") ' Here is the error
If MyMenu.PleaseStopAll Then Exit For
Next
End Sub
End Module
The "status update" line tells that this task cannot interact with its parent task.
If I remove this line, the code runs just fine : code breaks when I ask to, and form remains clickable.
I tried with BackgroundWorker, but it needs actual progression to report something with ReportProgress (in increasing %) do I cannot just give a status (as string)
Pass anything else via a variable or structure
BackgroundWorker1.WorkerReportsProgress = True '<Make sure this is set
BackgroundWorker1.RunWorkerAsync()
Private SomeStatus As String
Private Sub BackgroundWorker1_DoWork(sender As Object, e As System.ComponentModel.DoWorkEventArgs) Handles BackgroundWorker1.DoWork
SomeStatus = "Hello"
BackgroundWorker1.ReportProgress(0)
End Sub
Private Sub BackgroundWorker1_ProgressChanged(sender As Object, e As System.ComponentModel.ProgressChangedEventArgs) Handles BackgroundWorker1.ProgressChanged
ShowSOmeMEssage(SomeStatus)
End Sub
FYI: The progress thing is only so you can call a routine on the main UI thread. You can set as many thread-safe variables as you want on the form from within the backgroundworker. It's just updating the UI that's bothersome, though there is a workaround for that too.

RichTextBox not being updated by a Delegate / BackGroundWorker

I have a RichTextBox on a Windows Form that I wish to update with 'checkpoints' as I go through an XML building process.
I have a Class called 'LogFiles' that has the Delegate for the RichTextBox
Public Delegate Sub AppendRichTextBoxDelegate(ByVal RTB As RichTextBox, ByVal txt As String)
In that Class I have the Sub to Append the RTB
Public Shared Sub AppendRichTextBox(ByVal RTB As RichTextBox, ByVal txt As String)
Try
If RTB.InvokeRequired Then
RTB.Invoke(New AppendRichTextBoxDelegate(AddressOf AppendRichTextBox), New Object() {RTB, txt})
Else
RTB.AppendText(txt & Environment.NewLine)
End If
Catch ex As Exception
MsgBox(MsgBox(ex.Message & Environment.NewLine & Environment.NewLine & ex.StackTrace))
End Try
End Sub
Now when I call my BackGroundWorker
Public Sub BGW1ProcessFile_DoWork(sender As Object, e As System.ComponentModel.DoWorkEventArgs) Handles BGW1ProcessFile.DoWork
LogFiles.AppendRichTextBox(LogFileRTB, "Starting")
LogFiles.CreateLog()
Interchange.StartXML()
End Sub
That AppendRichTextBox works as expected and updates the RTB in real time, HOWEVER when I then stop into 'Interchange.StartXML' that also contains an AppendRichTextBox it does not update the RTB from within that Sub.
When the BackGroundWorker finishes the AppendRichTextBox for for the RunWorkerCompleted Event works as expected.
Why does the AppendRichTextBox Sub I have created with the delegate work on the BackGroundWorker Process but does not work on the Sub that is launched as part of the BackGroundWorker Process?
Its pickling my head a little bit, any assistance would be appreciated.
Regards,
James
As explained via comments, the BackgroundWorker is precisely meant to deal with the 2-thread situations involving GUI + another thread. That's why its in-built functionalities can gracefully account for the most likely situations.
In your case (i.e., regularly updating GUI elements from the BGW thread), you should rely on the ProgressChanged event. Here you have a sample code clearly showing how to use it:
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
BGW1ProcessFile.WorkerReportsProgress = True
BGW1ProcessFile.RunWorkerAsync()
End Sub
Private Sub BGW1ProcessFile_ProgressChanged(sender As Object, e As System.ComponentModel.ProgressChangedEventArgs) Handles BGW1ProcessFile.ProgressChanged
'You can include here as complex modifications on the GUI as
'required. Just store anything in e.UserState, as shown below
RTB.Text = RTB.Text & e.UserState.ToString & Environment.NewLine
End Sub
Private Sub modifyRTB(caseNo As Integer)
'Here you are calling the aforementioned ProgressChanged event.
BGW1ProcessFile.ReportProgress(0, "This is case " & caseNo.ToString)
End Sub
Private Sub BGW1ProcessFile_DoWork(sender As Object, e As System.ComponentModel.DoWorkEventArgs) Handles BGW1ProcessFile.DoWork
BGW1ProcessFile.ReportProgress(0, "We are in the BGW thread now")
modifyRTB(1)
modifyRTB(2)
modifyRTB(3)
End Sub
Private Sub BGW1ProcessFile_RunWorkerCompleted(sender As Object, e As System.ComponentModel.RunWorkerCompletedEventArgs) Handles BGW1ProcessFile.RunWorkerCompleted
RTB.Text = RTB.Text & Environment.NewLine & "We are outside the BWG thread now"
End Sub

BackgroundWorker freezes GUI

I have read other posts about this but I still can't seem to get it to work right.
Whenever my BackgroundWorker begins to do work, my function API.CheckForUpdate causes the GUI to hang. I can't click on anything. It only freezes for half a second, but is enough to notice.
How can I fix this? Should I dive deeper into API.CheckForUpdate and run individual threads on particular statements, or can I just have an all-inclusive thread that handles this? API.CheckForUpdate does not reference anything in Form1.
Also, I presume Form1_Load is not the best place to put the RunWorkerAsync call. Where is a better spot?
'Declarations
Dim ApplicationUpdate As BackgroundWorker = New BackgroundWorker
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
ApplicationUpdate.WorkerSupportsCancellation = True
ApplicationUpdate.WorkerReportsProgress = True
AddHandler ApplicationUpdate.DoWork, AddressOf ApplicationUpdate_DoWork
AddHandler ApplicationUpdate.ProgressChanged, AddressOf ApplicationUpdate_ProgressChanged
AddHandler ApplicationUpdate.RunWorkerCompleted, AddressOf ApplicationUpdate_RunWorkerCompleted
ApplicationUpdate.RunWorkerAsync()
End Sub
Private Sub ApplicationUpdate_DoWork(ByVal sender As Object, ByVal e As DoWorkEventArgs)
'Check for an update (get the latest version)
Dim LatestVersion = API.CheckForUpdate
End Sub
Private Sub ApplicationUpdate_ProgressChanged(ByVal sender As Object, ByVal e As ProgressChangedEventArgs)
'Nothing here
End Sub
Private Sub ApplicationUpdate_RunWorkerCompleted(ByVal sender As Object, ByVal e As RunWorkerCompletedEventArgs)
'Work completed
MsgBox("Done")
End Sub
Its not a background worker Fix but if you don't mind walking around and not finding the answer, you can code like so:
Keep in mind when you first Start a Thread and you are coding in a Model you MUST pass (me) into the initial thread because of VB having a concept of "Default Form Instances". For every Form in the application's namespace, there will be a default instance created in the My namespace under the Forms property.
and that is just adding an additional parameter like so
----------------------/ Starting Main Thread /-----------------------------------
Private Sub FindCustomerLocation()
Dim Findcontractor_Thread As New Thread(AddressOf **FindContractor_ThreadExecute**)
Findcontractor_Thread.Priority = ThreadPriority.AboveNormal
Findcontractor_Thread.Start(me)
End Sub
------------------/ Running Thread /---------------
Private Sub **FindContractor_ThreadExecute**(beginform as *NameOfFormComingFrom*)
Dim threadControls(1) As Object
threadControls(0) = Me.XamDataGrid1
threadControls(1) = Me.WebBrowserMap
**FindContractor_WorkingThread**(threadControls,beginform) ' ANY UI Calls back to the Main UI Thread MUST be delegated and Invoked
End Sub
------------------/ How to Set UI Calls from a Thread / ---------------------
Delegate Sub **FindContractor_WorkingThread**(s As Integer,beginform as *NameOfFormComingFrom*)
Sub **FindContractor_WorkingThreadInvoke**(ByVal s As Integer,beginform as *NameOfFormComingFrom*)
If beginform.mouse.InvokeRequired Then
Dim d As New FindContractor_WorkingThread(AddressOf FindContractor_WorkingThreadInvoke)
beginform.Invoke(d, New Object() {s,beginform})
Else
beginform.Mouse.OverrideCursor = Cursors.Wait
'Do something...
beginform.Mouse.OverrideCursor = Nothing
End If
End Sub
Sources From Pakks Answer Tested!
Try starting the process outside the Load event. Create a Timer and start it on the Load event, and then handle the event for the tick:
Private Sub Timer1_Tick(sender As Object, e As EventArgs) Handles Timer1.Tick
Timer1.Enabled = False
ApplicationUpdate.RunWorkerAsync()
End Sub

Iteration Not Working As Intended

I'm using a DO interation to loop a function I'm using to test for internet connectivity. The code is working fine, except that when one of the tests is satisfied the loop stops. I want this to continue in the background while the program is running. How can I get this to work?
Private Sub checkInternet()
Dim InetChecker As Boolean
InetChecker = CheckForInternetConnection()
Do While LabelCount.Text <> ""
Thread.Sleep(10)
If InetChecker = True Then
Dim image = My.Resources.greenbar
PictureBox4.Image = image
Else
Thread.Sleep(10)
Dim image = My.Resources.redbar
PictureBox4.Image = image
'NoInetConnError.Show()
End If
Loop
End Sub
Your assistance would be greatly appreciated, thanks.
Put a BackgroundWorker on your form (you will find it in the Components section of the Toolbox).
In the Properties window set WorkerReportsProgress to True for your BackgroundWorker.
Insert the following code to your form
Private connected As Boolean
Private Sub BackgroundWorker1_DoWork(ByVal sender As Object, ByVal e As DoWorkEventArgs) _
Handles BackgroundWorker1.DoWork
While True
Dim online = CheckForInternetConnection()
If online <> connected Then
connected = online
BackgroundWorker1.ReportProgress(CInt(online))
End If
Thread.Sleep(500)
End While
End Sub
Private Sub BackgroundWorker1_ProgressChanged(ByVal sender As Object, ByVal e As ProgressChangedEventArgs) _
Handles BackgroundWorker1.ProgressChanged
Dim online As Boolean = CBool(e.ProgressPercentage)
If online Then
PictureBox4.Image = My.Resources.greenbar
Else
PictureBox4.Image = My.Resources.redbar
End If
End Sub
Private Sub Form_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) _
Handles MyBase.Load
' Start the background worker
BackgroundWorker1.RunWorkerAsync()
End Sub
Note that Sub BackgroundWorker1_DoWork runs on a separate thread and does not freeze your form while it is running.
It would be best to do something like this in a Timer and not in a loop.
Private Sub Timer1_Tick(ByVal sender As Object, ByVal e As EventArgs) Handles Timer1.Tick
If CheckForInternetConnection Then
PictureBox4.Image = My.Resources.greenbar
Else
PictureBox4.Image = My.Resources.redbar
End If
End Sub
If you have access to .Net framework 3+ then you could use the DispatcherTimer class which essentially creates an interval (set at whatever length you require) which you can handle the tick event for. When the tick event is raised, you can do your internet connection check.
Modifying the MSDN example for your situation, you could do something like this:
' DispatcherTimer setup
dispatcherTimer = New Threading.DispatcherTimer()
AddHandler dispatcherTimer.Tick, AddressOf dispatcherTimer_Tick
dispatcherTimer.Interval = New TimeSpan(0,0,1) ' Or however long you want
dispatcherTimer.Start()
Private Sub dispatcherTimer_Tick(ByVal sender As Object, ByVal e As EventArgs)
' Checks to see whether an internet connection is still available etc
checkInternet()
' Forcing the CommandManager to raise the RequerySuggested event
CommandManager.InvalidateRequerySuggested()
End Sub

VB.Net Asynchronous background worker inside a loop

Im using Vb.net visual studio 2008. i have got a process(system.diagnostics.process) to be run in background and it updates the Ui thread progressbar and a label and i have worked it using backgroundworker.runworkerAsync. Now the problem is i have to work the same process for a number of time with different inputs.
The code block is :
Private Sub fnStartEvent(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnStart.click
Dim inputFolder = Directory.GetFiles(textboxSourceFolder.text)
Dim currentNumberOfFile As Integer=1
For each Files in inputFolder
Dim arguments As Object = Files
updateProgressStatus(0, currentNumberOfFile)
BackgroundWorker1.WorkerReportsProgress = True
BackgroundWorker1.RunWorkerAsync(New Object() {arguments})
currentNumberOfFile += 1
Next
End Sub
Private Sub BackgroundWorker1_DoWork(ByVal sender As System.Object, ByVal e As System.ComponentModel.DoWorkEventArgs) Handles BackgroundWorker1.DoWork
'uses the arguments
Dim Bg_process As System.Diagnostics.Process = New System.Diagnostics.Process
With Bg_process.StartInfo
.Arguments = str_arguments
.FileName = ffmpegPath
.CreateNoWindow = True
.UseShellExecute = False
.RedirectStandardOutput = True
.RedirectStandardError = True
End With
Bg_process.Start()
Dim outputReader As StreamReader = Bg_process.StandardError
Dim output As String
While Not Bg_process.HasExited
output = outputReader.ReadLine()
BackgroundWorker1.ReportProgress(0, output)
Threading.Thread.Sleep(500)
End While
End Sub
Private Sub BackgroundWorker1_ProgressChanged(ByVal sender As Object, ByVal e As System.ComponentModel.ProgressChangedEventArgs) Handles BackgroundWorker1.ProgressChanged
' process args
updateProgressStatus(args(0), args(1) )
End Sub
Private Function BackgroundWorker1_RunWorkerCompleted(ByVal sender As Object, ByVal e As System.ComponentModel.RunWorkerCompletedEventArgs) Handles BackgroundWorker1.RunWorkerCompleted
Messagebox.Show(e.error.ToString)
End Try
Sub updateProgressStatus(ByVal progressValue As Integer, ByVal progressStatus As String)
progressBar.value = progressValue
lblprogressStatus.Text = progressStatus
End Sub
The problem in here is the fnStartEvent method once gets started, it calls BackgroundWorker1.runWorkerAsync process and that runs in a seperate thread and does not wait for the thread to complete and it moves to the next line of code i.e loops to the next item and it returns to same BackgroundWorker1.runWorkerAsync line and throws exception that it is already running.
Tried
1.
Private Sub fnStartEvent(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnStart.click
Dim inputFolder = Directory.GetFiles(textboxSourceFolder.text)
Dim currentNumberOfFile As Integer=1
For each Files in inputFolder
Dim arguments As Object = Files
updateProgressStatus(0, currentNumberOfFile)
BackgroundWorker1.WorkerReportsProgress = True
BackgroundWorker1.RunWorkerAsync(New Object() {arguments})
''
Do Until BackgroundWorker1.IsBusy
Thread.Sleep(500)
Loop
currentNumberOfFile += 1
Next
End Sub
But this does not updates the Ui thread used to denote progress.
2.Put the whole process inside the DoWork thread and loop for each file in there itself, but that to returns the cross thread error in progress bar updation.
what is the standard procedure in executing a loop for the backgroundworker to wait for finishing as well as update the UI without blocking it.
Whenever you use Thread.Sleep, you are actually sleeping the current thread that is hooked to your startup class (ie. Form). So, if you sleep your main thread, no UI updates will happen.
Per the MSDN article regarding BackgroundWorker.IsBusy, you need to throw an Application.DoEvents. Code below is copied from the article linked above.
Private Sub downloadButton_Click( _
ByVal sender As Object, _
ByVal e As EventArgs) _
Handles downloadButton.Click
' Start the download operation in the background.
Me.backgroundWorker1.RunWorkerAsync()
' Disable the button for the duration of the download.
Me.downloadButton.Enabled = False
' Once you have started the background thread you
' can exit the handler and the application will
' wait until the RunWorkerCompleted event is raised.
' If you want to do something else in the main thread,
' such as update a progress bar, you can do so in a loop
' while checking IsBusy to see if the background task is
' still running.
While Me.backgroundWorker1.IsBusy
progressBar1.Increment(1)
' Keep UI messages moving, so the form remains
' responsive during the asynchronous operation.
Application.DoEvents()
End While
End Sub