Backgroundworker in VB not working? - vb.net

Im writing an app that takes and copy's folders (selected by the user in a listbox) to a specified location, it has a progress bar that should run the whole time but it seems that the backgroundworker isnt catching the process.. it just freezes the UI until the copy is complete.. when I put in a message between copy's the message works so I know it is capable of working.. im missing something..
Private Sub Button4_Click_1(sender As Object, e As EventArgs) Handles Btn_SaveApps.Click
bw.WorkerSupportsCancellation = True
bw.WorkerReportsProgress = True
AddHandler bw.DoWork, AddressOf bw_DoWork
AddHandler bw.ProgressChanged, AddressOf bw_ProgressChanged
AddHandler bw.RunWorkerCompleted, AddressOf bw_RunWorkerCompleted
If Not bw.IsBusy = True Then
bw.RunWorkerAsync(Module1.SaveApp)
End If
End Sub
Module Module1
Function SaveApp() As Process
Dim WinStrApps As String = ("C:\TransferFrom")
Form1.tbProgress.Style = ProgressBarStyle.Marquee
For Each Item In Form1.Selected_Apps.SelectedItems
On Error Resume Next
Form1.tbProgress.Visible = True
Dim FileLoc = ("C:\TransferTo\")
If System.IO.Directory.Exists(WinStrApps + Item) = True Then
Directory.CreateDirectory(FileLoc + Item)
My.Computer.FileSystem.CopyDirectory(WinStrApps + Item, FileLoc + Item)
Else
MsgBox(Item + " does not exist, or is a system App. Please choose another application")
End If
Next
MsgBox("Completed saving the applications")
End Function

I think you may have called the thread wrong, the thread has to begin in the sub bw_DoWork
Private Sub bw_DoWork(ByVal sender As Object, ByVal e As DoWorkEventArgs)
Module1.SaveApp
end sub
and any updates passed back to the UI thread need to be passed to
Private Sub bw_ProgressChanged(ByVal sender As Object, ByVal e As ProgressChangedEventArgs)
Form1.tbProgress.value = e.ProgressPercentage
End Sub
however you wont be able to access the update routine from your sub as the routine is private of Form1... you may have to create a delegate in your module.

Related

Background Worker ReportProgress not firing 2

I am working on a Legacy app and this is driving me crazy, every example I have seen addressing this issue appears to do what I am doing but for some reason my reportProgress event refuses to fire:
Private _worker as BackgroundWorker
Private Sub btnEditCheckSymbol_Click (ByVal sender As Object, ByVal e As EventArgs) Handles btnEditCheckSymbol.Click
_worker = new BackgroundWorker()
_worker.WorkerSupportsCancellation = True
_worker.WorkerReportsProgress = True
AddHandler _worker.DoWork, AddressOf worker_DoWork
AddHandler _worker.ProgressChanged, AddressOf worker_ProgressChanged
AddHandler _worker.RunWorkerCompleted, AddressOf worker_RunWorkerCompleted
_worker.RunWorkerAsync()
'Other long running code
If _worker.IsBusy Then
_worker.CancelAsync()
End If
End Sub
Private Sub worker_DoWork(ByVal sender As Object, ByVal e As DoWorkEventArgs) _
For i As Integer = 60 To 0 step -1
If _worker.CancellationPending Then
e.Cancel = True
return
End If
_worker.ReportProgress(i)
Next
End Sub
Private Sub worker_ProgressChanged(ByVal sender As Object, ByVal e As ProgressChangedEventArgs) _
lbEditCheckSymbol.Invoke(
Sub ()
with lbEditCheckSymbol
.Items.Clear()
.Items.Add("Please wait for results: " + e.ProgressPercentage)
end With
End Sub)
Thread.Sleep(100)
End Sub
Private Sub worker_RunWorkerCompleted(ByVal sender As Object, ByVal e As RunWorkerCompletedEventArgs) _
with lbEditCheckSymbol
.Items.Clear()
If Not e.Cancelled then
lbEditCheckSymbol.Invoke(
Sub ()
with lbEditCheckSymbol
.Items.Add("Results should have arrived by now... um wait some more?")
end With
End Sub)
End If
end With
End Sub
The DoWork is called and it burns through the loop never invoking the ProgressChanged event, I checked in the debugger and the ProgressChanged is registered. I mostly work in C# so am I missing some VB voodoo here?
UPDATE
I have abandoned the use of the background worker in favor of the TPL library. Less code, easier to debug and I actually got it to work so win, win, win.
If you had better error handlers around your threaded work, you would catch the access violation when your background thread tries to access objects on the foreground thread. To reach-across threads like that, you need some delegates and use BeginInvoke instead of Invoke to call over to the right thread.
This code might not be perfect, but it should be pretty close
Private _worker as BackgroundWorker
Public Delegate Sub ProgressChangedDelegateType(percent As Integer)
Public Delegate Sub RunWorkerCompletedDelegateType(cancelled As Boolean)
Private progressChangedDelegate As ProgressChangedDelegateType
Private runWorkerCompletedDelegate AS RunWorkerCompletedDelegateType
Public Sub Form_Load()
'bind your delegates in your form load event or in the class constructor
progressChangedDelegate = AddressOf ProgressChanged
runWorkerCompletedDelegate = AddressOf RunWorkerCompleted
'and all of your other stuff in your form_load() event handler...
End Sub
Private Sub btnEditCheckSymbol_Click (ByVal sender As Object, ByVal e As EventArgs) Handles btnEditCheckSymbol.Click
_worker = new BackgroundWorker()
_worker.WorkerSupportsCancellation = True
_worker.WorkerReportsProgress = True
AddHandler _worker.DoWork, AddressOf worker_DoWork
AddHandler _worker.ProgressChanged, AddressOf worker_ProgressChanged
AddHandler _worker.RunWorkerCompleted, AddressOf worker_RunWorkerCompleted
_worker.RunWorkerAsync()
'Other long running code
If _worker.IsBusy Then
_worker.CancelAsync()
End If
End Sub
Private Sub worker_DoWork(ByVal sender As Object, ByVal e As DoWorkEventArgs) _
For i As Integer = 60 To 0 step -1
If _worker.CancellationPending Then
e.Cancel = True
return
End If
_worker.ReportProgress(i)
Next
End Sub
Private Sub worker_ProgressChanged(ByVal sender As Object, ByVal e As ProgressChangedEventArgs)
If lbEditCheckSymbol.InvokeRequired Then
BeginInvoke(progressChangedDelgate, {e.ProgressPercentage})
Else
ProgressChanged(e.ProgressPercentage)
End If
End Sub
Public Sub ProgressChanged(percent As Integer)
Try
with lbEditCheckSymbol
.Items.Clear()
.Items.Add("Please wait for results: " + percent.ToString())
end With
DoEvents() 'forces a UI update
Catch Ex As Exception
Debug.WriteLine(ex.message) 'put a breakpoint here, just in case
End Try
Thread.Sleep(100)
End Sub
Private Sub worker_RunWorkerCompleted(ByVal sender As Object, ByVal e As RunWorkerCompletedEventArgs) _
If lbEditCheckSymbol.InvokeRequired Then
BeginInvoke(runWorkerCompletedDelegate, {e.Cancelled})
Else
RunWorkerCompleted(e.Cancelled)
End If
End Sub
Public Sub RunWorkerCompleted(cancelled As Boolean)
Try
with lbEditCheckSymbol
.Items.Clear()
If Not cancelled then
.Items.Add("Results should have arrived by now... um wait some more?")
end With
DoEvents() 'Forces a UI update
Catch Ex As Exception
Debug.WriteLine(ex.message) 'put a breakpoint here, just in case
End Try
End Sub

How to wait for BackgroundWorker to finish without killing ProgressBar?

The application is doing a lot more than this, but I have narrowed down the issue with the example below.
When bgwDone.WaitOne() is commented out, the progress bar works fine, cancel button is effective, but execution continues before the background process is complete.
When bgwDone.WaitOne() is applied, the ProgressForm is visible but not enabled, so processing cannot be cancelled and progress bar does not refresh, and the most confusing part, Msgbox("1") does not execute. I only see Msgbox("2") after the background worker finishes. I am utterly perplexed.
Imports System.ComponentModel
Public Class Form1
Private WithEvents bgw As BackgroundWorker
Private Event bgwCancelled()
Private bgwDone As New System.Threading.AutoResetEvent(False)
'Allows ProgressForm to cancel execution
Public Sub bgwCancelAsync()
RaiseEvent bgwCancelled()
End Sub
Private Sub bgw_Cancelled_by_ProgressForm() Handles Me.bgwCancelled
bgw.CancelAsync()
End Sub
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
Cursor = Cursors.WaitCursor
bgw = New BackgroundWorker
bgw.WorkerReportsProgress = True
bgw.WorkerSupportsCancellation = True
If bgw.IsBusy = False Then
ProgressForm.Show()
bgw.RunWorkerAsync(10)
End If
'********THIS LINE: bgwDone.WaitOne() MAKES A BIG DIFFERENCE*******
bgwDone.WaitOne()
MsgBox("1")
MsgBox("2")
Cursor = Cursors.Default
End Sub
'BackgroundWorker.RunWorkerAsync raises the DoWork event
Private Sub bgw_DoWork(sender As Object, e As DoWorkEventArgs) Handles bgw.DoWork
Dim numToDo As Integer = CInt(e.Argument)
For n As Integer = 1 To numToDo
If bgw.CancellationPending Then
Exit For
End If
System.Threading.Thread.Sleep(200)
bgw.ReportProgress(n * 10)
Next
bgwDone.Set()
End Sub
'ReportProgress raises the ProgressChanged event
Private Sub bgw_ProgressChanged(sender As Object, e As ProgressChangedEventArgs) Handles bgw.ProgressChanged
ProgressForm.UpdateProgress(e.ProgressPercentage)
End Sub
Private Sub bgw_RunWorkerCompleted(sender As Object,
e As RunWorkerCompletedEventArgs) Handles bgw.RunWorkerCompleted
ProgressForm.Close()
End Sub
And my form with the ProgressBar:
Public Class ProgressForm
Private Sub ButtonCancel_Click(sender As Object, e As EventArgs) Handles ButtonCancel.Click
Form1.bgwCancelAsync()
End Sub
Public Sub UpdateProgress(pct As Integer)
ProgressBar1.Value = pct
ProgressBar1.Refresh()
End Sub
End Class
I am not sure what you are trying to accomplish. But it almost seems like some of your code is trying to defeat the purpose of a BackGroundWorker:
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
Cursor = Cursors.WaitCursor
bgw = New BackgroundWorker
...
If bgw.IsBusy = False Then
ProgressForm.Show()
bgw.RunWorkerAsync(10)
End If
bgwDone.WaitOne()
MsgBox("1")
MsgBox("2")
Cursor = Cursors.Default
End Sub
The purpose of a BackgroundWorker is to do some long running task on another thread and leave the UI responsive. I am not sure that a task that only "takes several seconds" qualifies as a long running task.
Given that, why use the WaitCursor while the BGW runs? The point to leaving the UI resposive is to allow the user to do other things in the meantime.
The test for bgw.IsBusy can never, ever be true - you just created it 3 lines earlier. Click the button again and you will create another BGW.
The rest of the code in the click looks like you want or expect the code to continue on the next line after the BGW completes. That's not how it works.
If the app cannot continue without those tasks being completed, disable anything that lets the user go elsewhere until the worker completes or:
Forego the worker and put the form in wait mode (Me.UseWaitCursor) until the stuff is loaded. This doesn't rule out a ProgressBar.
A dedicated Progress Form can make sense in cases where the app will use various workers at various times. A StatusBar can contain a ProgressBar and is much more subtle (and perhaps appropriate since it is a status element).
So, revised and using a form instance for the progress reporter:
MainForm
Private WithEvents bgw As BackgroundWorker
Private frmProg As ProgressForm
Public Sub New()
' This call is required by the designer.
InitializeComponent()
' Add any initialization after the InitializeComponent() call.
bgw = New BackgroundWorker
End Sub
Private Sub btnLoadAll_Click(sender As Object, e As EventArgs) Handles btnLoadAll.Click
bgw.WorkerReportsProgress = True
bgw.WorkerSupportsCancellation = True
If bgw.IsBusy = False Then
' create ProgressForm instance if needed
If frmProg Is Nothing Then frmProg = New ProgressForm
frmProg.Show()
bgw.RunWorkerAsync(78)
End If
btnLoadAll.Enabled = False
End Sub
Private Sub bgw_DoWork(sender As Object, e As DoWorkEventArgs) Handles bgw.DoWork
' multiple workers can use the same event
Dim thisWorker = DirectCast(sender, BackgroundWorker)
Dim count = Convert.ToInt32(e.Argument)
For n As Integer = 1 To count
If thisWorker.CancellationPending Then
Exit For
End If
' Fake work:
System.Threading.Thread.Sleep(50)
' dont assume the size of the job if
' there are multiple BGW or tasks
thisWorker.ReportProgress(Convert.ToInt32((n / count) * 100))
Next
End Sub
Private Sub bgw_ProgressChanged(sender As Object,
e As ProgressChangedEventArgs) Handles bgw.ProgressChanged
frmProg.UpdateProgress(e.ProgressPercentage)
End Sub
Private Sub bgw_RunWorkerCompleted(sender As Object,
e As RunWorkerCompletedEventArgs) Handles bgw.RunWorkerCompleted
If e.Error IsNot Nothing Then
'... ToDo
ElseIf e.Cancelled Then
'... ToDo
Else
frmProg.Close()
' avoid 'cannot access disposed object':
frmProg = Nothing
Me.btnNextStep.Enabled = True
btnLoadAll.Enabled = True
End If
End Sub
Rather than enabling a "Next" button, the app could automatically proceed. It depends on the app.

"For Each" loop : Application Freeze in Vb.net

I am using the following code to get the size of files inside a directory
and put it in Label1:
For Each foundFile As String In My.Computer.FileSystem.GetFiles( _
"\windows",Microsoft.VisualBasic.FileIO.SearchOption.SearchTopLevelOnly,_
"*.*")
Dim filesizelabel As System.IO.FileInfo = My.Computer.FileSystem.GetFileInfo(foundFile)
Label1.Text = Label1.Text + filesizelabel.Length
Next
The problem is that i have more than 50 for each loops (a system cleaning app).
When I run the loops my app freezes until the loops finish, even if I run one loop.
Is there a solution to make it show the name of the current file? I tried this as well, but it also froze my application:
label2.text = foundfile
The application does not respond to any click, until it finishes the loops. It shows the size in Label1 and the last scanned file in Label2. This also freezes the application:
system.threading.thread.sleep(100)
Is there any alternative to foreach or a solution to fix this issue?
Here's a quick example using Async/Await with a Button Click() Handler:
Private Async Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
Button1.Enabled = False
Await Task.Run(Sub()
' this runs in a different thread without blocking the GUI:
For Each foundFile As String In My.Computer.FileSystem.GetFiles(
"\windows", Microsoft.VisualBasic.FileIO.SearchOption.SearchTopLevelOnly, "*.*")
Dim filesizelabel As System.IO.FileInfo = My.Computer.FileSystem.GetFileInfo(foundFile)
' when you need to update the GUI:
Me.Invoke(Sub()
' ... do it in here ...
Label1.Text = Label1.Text + filesizelabel.Length
End Sub)
Next
End Sub)
Button1.Enabled = True
End Sub
For VB.Net 2010, try this instead:
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
Button1.Enabled = False
Dim T As New System.Threading.Thread(AddressOf Worker)
T.Start()
End Sub
Private Sub Worker()
' this runs in a different thread without blocking the GUI:
For Each foundFile As String In My.Computer.FileSystem.GetFiles(
"\windows", Microsoft.VisualBasic.FileIO.SearchOption.SearchTopLevelOnly, "*.*")
Dim filesizelabel As System.IO.FileInfo = My.Computer.FileSystem.GetFileInfo(foundFile)
' when you need to update the GUI:
Me.Invoke(Sub()
' ... do it in here ...
Label1.Text = Label1.Text + filesizelabel.Length
End Sub)
Next
Me.Invoke(Sub()
Button1.Enabled = True
End Sub)
End Sub
This is a prime candidate for a background worker.
Have a read about how they work, but at a high level the task is run in another thread with some events that you access in your main UI thread.
Private bw As BackgroundWorker = New BackgroundWorker
Private Sub buttonStart_Click(ByVal sender As System.Object, ByVal e As System.Windows.RoutedEventArgs)
If Not bw.IsBusy = True Then
' this will start the work
bw.RunWorkerAsync()
End If
End Sub
Private Sub buttonCancel_Click(ByVal sender As System.Object, ByVal e As System.Windows.RoutedEventArgs)
If bw.WorkerSupportsCancellation = True Then
' this will allow the user to cancel the work part way through
bw.CancelAsync()
End If
End Sub
Private Sub bw_DoWork(ByVal sender As Object, ByVal e As DoWorkEventArgs)
' your slow code goes here
End Sub
Private Sub bw_ProgressChanged(ByVal sender As Object, ByVal e As ProgressChangedEventArgs)
' you can update the UI here to show progress
End Sub
Private Sub bw_RunWorkerCompleted(ByVal sender As Object, ByVal e As RunWorkerCompletedEventArgs)
' your 'I've finished notification' code goes here
End Sub
Derek has put a bad code, it's not working and Idle mind code does not work on .NET 2.0
Dereks approach is working if code is complete, as below:
Private bw As BackgroundWorker = New BackgroundWorker
Private Sub app_Load(sender As Object, e As EventArgs) Handles MyBase.Load
AddHandler bw.DoWork, AddressOf bw_DoWork
AddHandler bw.ProgressChanged, AddressOf bw_ProgressChanged
AddHandler bw.RunWorkerCompleted, AddressOf bw_RunWorkerCompleted
..
End sub
Private Sub Btn_Click(sender As Object, e As EventArgs) Handles Btn.Click
If Not bw.IsBusy = True Then
' this will start the work
bw.RunWorkerAsync()
End If
End Sub
Private Sub bw_DoWork(ByVal sender As Object, ByVal e As DoWorkEventArgs)
'your work to not freeze form
end sub
Private Sub bw_ProgressChanged(ByVal sender As Object, ByVal e As ProgressChangedEventArgs)
' you can update the UI here to show progress
End Sub
Private Sub bw_RunWorkerCompleted(ByVal sender As Object, ByVal e As RunWorkerCompletedEventArgs)
' your 'I've finished notification' code goes here
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

Background Worker thread not ending on cancellation

I am having a little issue, I finally figured out how to add a background worker to my application now my only problem is it does not end the thread, or atleast not fast enough when I am clicking my cancel button. I must be doing something wrong. I would like for it to abort the thread as soon as the button is clicked. Is this feasable? My thread is by no means extensive.
I am going to post some examples of the way I am doing it.
Public Sub New()
InitializeComponent()
bw.WorkerReportsProgress = True
bw.WorkerSupportsCancellation = True
AddHandler bw.DoWork, AddressOf bw_DoWork
' AddHandler bw.ProgressChanged, AddressOf bw_ProgressChanged
AddHandler bw.RunWorkerCompleted, AddressOf bw_RunWorkerCompleted
End Sub
My DoWork Event
Private Sub bw_DoWork(ByVal sender As Object, ByVal e As DoWorkEventArgs)
If bw.CancellationPending = True Then
e.Cancel = True
WorkEventRunning = False
Else
CheckForIllegalCrossThreadCalls = False
Dim worker As BackgroundWorker = CType(sender, BackgroundWorker)
'my long winded event
' more of my long winded event.
End if
My Cancel button Code
Private Sub ToolStripButton2_Click_1(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles ToolStripButton2.Click
'Stop
If bw.WorkerSupportsCancellation = True Then
WorkEventRunning = False
bw.CancelAsync()
bw.Dispose()
End If
And my WorkCompleted
Private Sub bw_RunWorkerCompleted(ByVal sender As Object, ByVal e As RunWorkerCompletedEventArgs)
If e.Cancelled = True Then
'canceled
WorkEventRunning = False
ElseIf e.Error IsNot Nothing Then
MsgBox(e.Error.Message)
Else
'done
End If
End Sub
In the DoWork event, test for CancellationPending inside the long winded event.
Supposing that this long procedure contains a For-Loop or a ForEach then at every loop test for CancellationPending
For example:
Dim worker As BackgroundWorker = CType(sender, BackgroundWorker)
Dim x as Integer
For x = 0 to 1000000
If worker.CancellationPending = True Then
e.Cancel = True
Return
Else
.... ' Execute the works for this loop
End If
Next
The same test could be done inside the RunWorkerCompleted event because CancelAsync() sets the internal value for the CancellationPending property. See this question to look at the inner workings of CancelAsync()