Background Worker thread not ending on cancellation - vb.net

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()

Related

Cannot Await BackgroundWorker CancelAsync

I have a BackgroundWorker in my project that I want to restart in a single method. However, when I use the CancelAsync() method and then the RunWorkerAsync() method immediately after, the cancel hasn't completed and 2 instances of the BackgroundWorker are started and an InvalidOperationException is thrown. To combat this, I made the method async (it's a Sub method) and awaited the CancelAsync() to wait for it to finish. However, this returns an error that the expression doesn't produce a value. Is this due to the calling or the worker's method?
Worker Method:
Private Sub GIFworker(sender As Object, e As System.ComponentModel.DoWorkEventArgs) Handles bkwGIF.DoWork
Dim hum As Integer = e.Argument
If hum Mod 2 = 1 Then
While bkwGIF.CancellationPending = False
pbxRpsHum.Image = ilRPS.Images(0)
Thread.Sleep(1000)
pbxRpsHum.Image = ilRPS.Images(1)
Thread.Sleep(1000)
End While
Else
While bkwGIF.CancellationPending = False
pbxRpsHum.Image = ilRPS.Images(0)
Thread.Sleep(1000)
pbxRpsHum.Image = ilRPS.Images(3)
Thread.Sleep(1000)
End While
End If
End Sub
Calling CancelAsync() (hum is an integer):
Await bkwGIF.CancelAsync()
bkwGIF.RunWorkerAsync(hum)
Here's an example of how you cancel a BackgroundWorker:
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
'Raise the DoWork event in a worker thread.
Me.BackgroundWorker1.RunWorkerAsync()
End Sub
'This method is executed in a worker thread.
Private Sub BackgroundWorker1_DoWork(sender As Object, e As DoWorkEventArgs) Handles BackgroundWorker1.DoWork
Dim worker As BackgroundWorker = DirectCast(sender, BackgroundWorker)
For i As Integer = 1 To 100
If worker.CancellationPending Then
'The user has cancelled the background operation.
e.Cancel = True
Exit For
End If
'Raise the ProgressChanged event in the UI thread.
worker.ReportProgress(i, i & " iterations complete")
'Perform some time-consuming operation here.
Threading.Thread.Sleep(250)
Next i
End Sub
'This method is executed in the UI thread.
Private Sub BackgroundWorker1_ProgressChanged(sender As Object, e As ProgressChangedEventArgs) Handles BackgroundWorker1.ProgressChanged
Me.ProgressBar1.Value = e.ProgressPercentage
Me.Label1.Text = TryCast(e.UserState, String)
End Sub
'This method is executed in the UI thread.
Private Sub BackgroundWorker1_RunWorkerCompleted(sender As Object, e As RunWorkerCompletedEventArgs) Handles BackgroundWorker1.RunWorkerCompleted
If e.Cancelled Then
'The background operation was cancelled.
Me.Label1.Text = "Operation cancelled"
Else
'The background operation completed normally.
Me.Label1.Text = "Operation complete"
End If
End Sub
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
'Only cancel the background opertion if there is a background operation in progress.
If Me.BackgroundWorker1.IsBusy Then
Me.BackgroundWorker1.CancelAsync()
End If
End Sub
If you want to restart the background work on a cancellation then you would do so in the RunWorkerCompleted event handler when you detect that e.Cancelled is True.

VB Web Browser: Wait Until Element is Present

Hey!
Simple question, is there any way I can get a webbrowser to wait until an element is present? I'm not an amazing coder, so here's my best example of what I'm trying to do -
Do While PageLoaded = False
Dim OObject As Object
OObject = WebBrowser1.Document.GetElementById("Element-That-I-Need-Loaded")
If (OObject Is Nothing) Then
Pageloaded = False
Else
PageLoaded = True
End If
Loop
The problem with this is that the element doesn't have an ID, so when I'm trying to do something like click it, I have to do something like this -
For Each altelm As HtmlElement In WebBrowser1.Document.GetElementsByTagName("SPAN")
If altelm.GetAttribute("classname").ToString = "Classname-of-Element-I-Need-Loaded" Then
altelm.Focus()
altelm.InvokeMember("click")
End If
I apologise if this is worded poorly, I'm new to this :)
Thanks!
Use a timer that checks for the element periodically. Start the timer in the DocumentCompleted event, then stop it once the element has been found.
Private WithEvents WaitTimer As New Timer With {.Interval = 250}
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
WebBrowser1.Navigate("http://www.somewebsite.com/")
AddHandler WebBrowser1.DocumentCompleted, AddressOf SomeWebsite_DocumentCompleted
End Sub
Private Sub SomeWebsite_DocumentCompleted(sender As Object, e As WebBrowserDocumentCompletedEventArgs)
WaitTimer.Start()
RemoveHandler WebBrowser1.DocumentCompleted, AddressOf SomeWebsite_DocumentCompleted
End Sub
Private Sub WaitTimer_Tick(sender As Object, e As EventArgs) Handles WaitTimer.Tick
'Look for the element every time the timer ticks.
For Each altelm As HtmlElement In WebBrowser1.Document.GetElementsByTagName("SPAN")
If altelm.GetAttribute("className") = "Classname-of-Element-I-Need-Loaded" Then 'No need to call ToString() since GetAttribute() already returns a string.
altelm.Focus()
altelm.InvokeMember("click")
WaitTimer.Stop() 'Element found, stop the timer.
End If
Next
End Sub

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

Backgroundworker in VB not working?

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.

vb.net - how do i fix crossthreading with background worker

In VB.Net I've created a background worker which seems to work. It is set up like this:
Private bw As BackgroundWorker = New BackgroundWorker
Public Sub construct_configure()
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
Then I have the doWork Sub set up like this:
Private Sub bw_DoWork(ByVal sender As Object, ByVal e As DoWorkEventArgs)
' create the picture box
pic_holder = New PictureBox()
' Show GIF and disable whatever you need to
With pic_holder
.Image = My.Resources.counter_gif
.Size = New Size(200, 100)
.Location = New Point(0, 0)
.Visible = True
.BackColor = Color.Red
End With
Main.Controls.Add(pic_holder)
pic_holder.BringToFront()
MessageBox.Show("worker started")
End Sub
The message box shows up when I click on my button to call the Sub, but the animated gif never does. I'm trying to simply set up an indicator that there is some background work going on. The gif shows up when I set it up in the parent sub.
Also, I'd like to place the gif in a panel that already exists, but every time I try to, I get a cross threading error that says the panel was created in another thread. How can I get around that?
Thanks.
It's recommended that if you want to invoke the main form you should do this in the ProgressChanged event. The bw_ProgressChanged is called on the same thread as the main form.
Private Sub bw_DoWork(sender As Object, e As DoWorkEventArgs) Handles bw.DoWork
'WORKER THREAD, do not invoke main form.
Dim worker As BackgroundWorker = CType(sender, BackgroundWorker)
'Report start
worker.ReportProgress(0)
'Do some work...
Threading.Thread.Sleep(5000)
'Report end.
worker.ReportProgress(100)
End Sub
Private Sub bw_ProgressChanged(sender As Object, e As ProgressChangedEventArgs) Handles bw.ProgressChanged
'MAIN THREAD, safe to invoke main form.
If (e.ProgressPercentage = 0) Then
'Started: Do something...
ElseIf (e.ProgressPercentage = 100) Then
'Stopped: Do something...
End If
End Sub