Why does my delegate called from backgroundworker runs in main thread - vb.net

I am writing a program that will allow me to run a number of different hardware test routines.
The routines can be quite time consuming, lasting up to 30mins. During this time, I have to control a selection of test equipment to set up conditions and take measurements.
I was thinking that using a background worker to carry out the tasks would be ideal, and allow the UI to stay responsive. This worked well until one of the routines requires me to take measurements every 1.5seconds. I am using a system timer to trigger these events. The timer is created and started in the doWork sub of the background worker, however, I find that the delegate is running in the main(UI) thread and not in the background worker thread as I thought.
Am I doing something wrong? I have attached the main parts of a simplified program that has the same structure.
Private Sub getMeasurement()
' Runs in backgroundWorker thread
Me.TextBox2.Text = (System.DateTime.Now - startTime).TotalSeconds.ToString
startTime = System.DateTime.Now
Debug.Print("Thread name is " & Thread.CurrentThread.Name & ", ID = " & Thread.CurrentThread.ManagedThreadId)
End Sub
Private Sub OnTimedEvent()
'Runs in own thread, Calls getMeasurement which runs in BackgroundWorker thread
Thread.CurrentThread.Name = "OTE"
Debug.Print("In OnTimedEvent, thread = " & Thread.CurrentThread.Name & ", ID = " & Thread.CurrentThread.ManagedThreadId)
Dim ServiceTimerDelegate As New ServiceTimerDelegate(AddressOf getMeasurement)
Me.BeginInvoke(ServiceTimerDelegate)
End Sub
Private Sub backgroundWorker1_DoWork(ByVal sender As System.Object, _
ByVal e As DoWorkEventArgs) Handles BackgroundWorker1.DoWork
Thread.CurrentThread.Name = "BW1"
mTimer = New Timers.Timer(1490) ' 14.9secs (allow for some latency)
AddHandler mTimer.Elapsed, New Timers.ElapsedEventHandler(AddressOf OnTimedEvent)
Dim worker As BackgroundWorker = CType(sender, BackgroundWorker)
Dim i As Integer
'main timer for measurements every 1.5secs (may change to take interval from UI)
Select Case e.Argument
Case "Sunday"
mTimer.Start()
Debug.Print("Thread in DoWork = " & Thread.CurrentThread.Name & ", ID = " & Thread.CurrentThread.ManagedThreadId)
startTime = System.DateTime.Now
'main loop for temperature ramping
For i = 20 To 70
If (worker.CancellationPending = True) Then
e.Cancel = True
Else
Thread.Sleep(500)
worker.ReportProgress((i - 19) * (100 / 50))
i += 1
End If
Next
Case Else
e.Cancel = True
End Select
End Sub

Three things are confusing here, and make me think that the code you've posted above doesn't actually use the BackgroundWorker for its function.
The DoWork event always runs in a separate thread from the UI. That's the entire point of it. ReportProgress runs in the UI thread, but you're not making use of that.
You don't actually appear to be running the BackgroundWorker at all in the above code. Rather, you seem to be using a ServiceTimerDelegate - not 100% sure I know what that is. You've defined a BackgroundWorker within your DoWork handler (which makes no sense), but you never seem to be actually hooking a BackgroundWorker up to that handler nor calling RunWorkerAsync on it.
Even if you were running getMeasurement() in a BackgroundWorker, it should fail - you're altering the contents of a UI item, which should cause an exception due to InvalidCrossThreadAccess. If you need to alter a UI control, use the ReportProgress event, which occurs within the UI thread.

Related

How can I run code in a background thread and still access the UI?

I made a file search program in visual studio on windows 10 using .net lang,
My problem starts from form1 with a "dim frm2 as form2 = new form2" call,
after the new form being shown i start a while loop on form1 that feeds data into a listbox in form 2:
1)form1 call form2 and show it.
2)form1 start a while loop.
3)inside the while loop data being fed to listbox1 in frm2
Now everything works on windows 10, the while loop can run as much as it needs without any trouble, the window can loose focus and regain focus without showing any "Not Responding.." msgs or white\black screens..
But, when i take the software to my friend computer which is running windows 7, install all required frameworks and visual studio itself, run it from the .sln in debug mode, and do the same search on the same folder the results are:
1) the while loop runs smoothly as long as form 2 dont loose focus
(something that doesnt happen on windows 10)
2) when i click anywhere on the screen the software loose focus what
causes 1) to happen (black screen\white screen\not responding etc..)
3) if i wait the time needed for the loop and dont click anywhere else
it keeps running smoohtly, updating a label like it should with the
amount of files found.. and even finish the loop with 100% success
(again unless i click somewhere)
Code Example:
Sub ScanButtonInForm1()
Dim frm2 As Form2 = New Form2
frm2.Show()
Dim AlreadyScanned As HashSet(Of String) = New HashSet(Of String)
Dim stack As New Stack(Of String)
stack.Push("...Directoy To Start The Search From...")
Do While (stack.Count > 0)
frm2.Label4.Text = "-- Mapping Files... -- Folders Left:" + stack.Count.ToString + " -- Files Found:" + frm2.ListBox1.Items.Count.ToString + " --"
frm2.Label4.Refresh()
Dim ScanDir As String = stack.Pop
If AlreadyScanned.Add(ScanDir) Then
Try
Try
Try
Dim directoryName As String
For Each directoryName In System.IO.Directory.GetDirectories(ScanDir)
stack.Push(directoryName)
frm2.Label4.Text = "-- Mapping Files... -- Folders Left:" + stack.Count.ToString + " -- Files Found:" + frm2.ListBox1.Items.Count.ToString + " --"
frm2.Label4.Refresh()
Next
frm2.ListBox1.Items.AddRange(System.IO.Directory.GetFiles(ScanDir, "*.*", System.IO.SearchOption.AllDirectories))
Catch ex5 As UnauthorizedAccessException
End Try
Catch ex2 As System.IO.PathTooLongException
End Try
Catch ex4 As System.IO.DirectoryNotFoundException
End Try
End If
Loop
End Sub
My conclusions was simple!
1) windows 7 dont support live ui (label) update from a while loop
called from a button...
2) windows 7 could possibly support a new
thread running the same loop
i think mabye if i run all the code in a thread mabye the ui will remain responsive
(by the way the UI is not responsive in windows 10 but i still see
the label refresh and nothing crashes when form loose focus..)
so i know how to do that but i also know that if i do that a thread will not be able to update a listbox or a label in a form and refresh it..
so the thread will need to update an external file with the data and the form2 will need to read that data live from the file but will it make the same problems? i have no idea what to do.. can use some help and tips. THANK YOU!
I must menttion the fact that the loop is working on windows 10 without a responsive UI means i cant click on any button but i can
still see the label refresh BUT on windows 7 everything works the same
UNLESS i click somewhere, no matter where i click on windows the loop
crashes
im using framework 4.6.2 developer
While I'm glad you found a solution, I advise against using Application.DoEvents() because it is bad practice.
Please see this blog post: Keeping your UI Responsive and the Dangers of Application.DoEvents.
Simply put, Application.DoEvents() is a dirty workaround that makes your UI seem responsive because it forces the UI thread to handle all currently available window messages. WM_PAINT is one of those messages which is why your window redraws.
However this has some backsides to it... For instance:
If you were to close the form during this "background" process it would most likely throw an error.
Another backside is that if the ScanButtonInForm1() method is called by the click of a button you'd be able to click that button again (unless you set Enabled = False) and starting the process once more, which brings us to yet another backside:
The more Application.DoEvents()-loops you start the more you occupy the UI thread, which will cause your CPU usage to rise rather quickly. Since every loop is run in the same thread your processor cannot schedule the work over different cores nor threads, so your code will always run on one core, eating as much CPU as possible.
The replacement is, of course, proper multithreading (or the Task Parallel Library, whichever you prefer). Regular multithreading actually isn't that hard to implement.
The basics
In order to create a new thread you only need to declare an instance of the Thread class and pass a delegate to the method you want the thread to run:
Dim myThread As New Thread(AddressOf <your method here>)
...then you should set its IsBackground property to True if you want it to close automatically when the program closes (otherwise it keeps the program open until the thread finishes).
Then you just call Start() and you have a running background thread!
Dim myThread As New Thread(AddressOf myThreadMethod)
myThread.IsBackground = True
myThread.Start()
Accessing the UI thread
The tricky part about multithreading is to marshal calls to the UI thread. A background thread generally cannot access elements (controls) on the UI thread because that might cause concurrency issues (two threads accessing the same control at the same time). Therefore you must marshal your calls to the UI by scheduling them for execution on the UI thread itself. That way you will no longer have the risk of concurrency because all UI related code is run on the UI thread.
To marhsal calls to the UI thread you use either of the Control.Invoke() or Control.BeginInvoke() methods. BeginInvoke() is the asynchronous version, which means it doesn't wait for the UI call to complete before it lets the background thread continue with its work.
One should also make sure to check the Control.InvokeRequired property, which tells you if you already are on the UI thread (in which case invoking is extremely unnecessary) or not.
The basic InvokeRequired/Invoke pattern looks like this (mostly for reference, keep reading below for shorter ways):
'This delegate will be used to tell Control.Invoke() which method we want to invoke on the UI thread.
Private Delegate Sub UpdateTextBoxDelegate(ByVal TargetTextBox As TextBox, ByVal Text As String)
Private Sub myThreadMethod() 'The method that our thread runs.
'Do some background stuff...
If Me.InvokeRequired = True Then '"Me" being the current form.
Me.Invoke(New UpdateTextBoxDelegate(AddressOf UpdateTextBox), TextBox1, "Status update!") 'We are in a background thread, therefore we must invoke.
Else
UpdateTextBox(TextBox1, "Status update!") 'We are on the UI thread, no invoking required.
End If
'Do some more background stuff...
End Sub
'This is the method that Control.Invoke() will execute.
Private Sub UpdateTextBox(ByVal TargetTextBox As TextBox, ByVal Text As String)
TargetTextBox.Text = Text
End Sub
New UpdateTextBoxDelegate(AddressOf UpdateTextBox) creates a new instance of the UpdateTextBoxDelegate that points to our UpdateTextBox method (the method to invoke on the UI).
However as of Visual Basic 2010 (10.0) and above you can use Lambda expressions which makes invoking much easier:
Private Sub myThreadMethod()
'Do some background stuff...
If Me.InvokeRequired = True Then '"Me" being the current form.
Me.Invoke(Sub() TextBox1.Text = "Status update!") 'We are in a background thread, therefore we must invoke.
Else
TextBox1.Text = "Status update!" 'We are on the UI thread, no invoking required.
End If
'Do some more background stuff...
End Sub
Now all you have to do is type Sub() and then continue typing code like if you were in a regular method:
If Me.InvokeRequired = True Then
Me.Invoke(Sub()
TextBox1.Text = "Status update!"
Me.Text = "Hello world!"
Label1.Location = New Point(128, 32)
ProgressBar1.Value += 1
End Sub)
Else
TextBox1.Text = "Status update!"
Me.Text = "Hello world!"
Label1.Location = New Point(128, 32)
ProgressBar1.Value += 1
End If
And that's how you marshal calls to the UI thread!
Making it simpler
To make it even more simple to invoke to the UI you can create an Extension method that does the invoking and InvokeRequired check for you.
Place this in a separate code file:
Imports System.Runtime.CompilerServices
Public Module Extensions
''' <summary>
''' Invokes the specified method on the calling control's thread (if necessary, otherwise on the current thread).
''' </summary>
''' <param name="Control">The control which's thread to invoke the method at.</param>
''' <param name="Method">The method to invoke.</param>
''' <param name="Parameters">The parameters to pass to the method (optional).</param>
''' <remarks></remarks>
<Extension()> _
Public Function InvokeIfRequired(ByVal Control As Control, ByVal Method As [Delegate], ByVal ParamArray Parameters As Object()) As Object
If Parameters IsNot Nothing AndAlso _
Parameters.Length = 0 Then Parameters = Nothing
If Control.InvokeRequired = True Then
Return Control.Invoke(Method, Parameters)
Else
Return Method.DynamicInvoke(Parameters)
End If
End Function
End Module
Now you only need to call this single method when you want to access the UI, no additional If-Then-Else required:
Private Sub myThreadMethod()
'Do some background stuff...
Me.InvokeIfRequired(Sub()
TextBox1.Text = "Status update!"
Me.Text = "Hello world!"
Label1.Location = New Point(128, 32)
End Sub)
'Do some more background stuff...
End Sub
Returning objects/data from the UI with InvokeIfRequired()
With my InvokeIfRequired() extension method you can also return objects or data from the UI thread in a simple manner. For instance if you want the width of a label:
Dim LabelWidth As Integer = Me.InvokeIfRequired(Function() Label1.Width)
Example
The following code will increment a counter that tells you for how long the thread has run:
Private Sub Button1_Click(sender As System.Object, e As System.EventArgs) Handles Button1.Click
Dim CounterThread As New Thread(AddressOf CounterThreadMethod)
CounterThread.IsBackground = True
CounterThread.Start()
Button1.Enabled = False 'Make the button unclickable (so that we cannot start yet another thread).
End Sub
Private Sub CounterThreadMethod()
Dim Time As Integer = 0
While True
Thread.Sleep(1000) 'Wait for approximately 1000 ms (1 second).
Time += 1
Me.InvokeIfRequired(Sub() Label1.Text = "Thread has been running for: " & Time & " seconds.")
End While
End Sub
Hope this helps!
The reason your application is freezing is that you are doing all the work on the UI thread. Check out Async and Await. It uses threading in the background but makes it way easier to manage. An example here:
https://stephenhaunts.com/2014/10/14/using-async-and-await-to-update-the-ui-thread/

Function takes priority over Timer?

I am new to vb.net and was wondering why my function (Run_Process) takes priority over the timer?
The timer (which starts the progress bar) runs after the function call even though the timer is set before the function.
Timer2.Start()
ListBox1.Items.Add("Backing up the registry, Please wait as this may take some time...")
ListBox1.ForeColor = Color.SlateBlue
MsgBox(Run_Process("CMD.exe", "/C regedit.exe /e C:\MMG\Regbackup.Reg"))
Timer2.Stop()
ListBox1.Items.Clear()
The function itself runs a cmd command.
The timer code is
Private Sub Timer2_Tick(sender As Object, e As EventArgs) Handles Timer2.Tick
ProgressBar1.Increment(1)
If ProgressBar1.Value = 100 And ListBox1.Items.Count() < 1 Then
Label1.Text = "Process complete with no obvious threats"
Button4.Enabled = False
Label1.ForeColor = Color.DarkGreen
Button1.Enabled = False
End If
Label3.Text = ProgressBar1.Value & (" %")
End Sub
I think you're mixing concepts.
You start the timer, and I suppose it's waiting Interval period prior to launch Tick Event. Meanwhile, your launching a CMD. Are you waiting for exit, or it's running in async mode?
Then, you stop the timer.... your progress bar could be on 10% or 4% ....
I mean: your cmd process and your timer, are not connected anyway.
How are launching CMD process?
Your timer it's growing from "1%" to "100%", in what interval? +1 every 1000 milisecs? 3000 milisecs? ... Your progress bar can be finished, and your CMD be still running.
If I were you, I would use a Thread or better a Task to perform this. But, you can consider to forget your Timer, and use a ProgressBar1.Style=Marquee", instead.
It looks like you use Run_Process from this question, the Call probably blocks until the process ends so your UI thread is not able to act on the timer event. I think you need to brush up on BackgroundWorker, Threadpool and likes.
In general you should use something like this pseudocode:
Start a BackgroundWorker that
Starts the Process
Reads the Output
Reports Progress (and ListBox Elements) via ReportProgress
Meanwhile your UI Thread
Handles the BackgroundWorkers ProgressChanged Event
Updates ListBox and ProgressBar
Exits when BackgroundWorker is done

BackgroundWorkers - ProgressChanged for static progress

I want to use a backgroundworker to poll a hardware sensor very frequently without leaving my UI inoperable.
Because the backgroundworker simply polls until interrupted - runtime is purely dictated by the user interrupting it - it has no change in progress so to speak.
If I call ReportProgress with a constant value, e.g. ReportProgress(1), will this still call ProgressChanged? I require ProgressChanged to update the UI in accordance with the latest poll data.
The value passed as first parameter to ReportProgress just serves at your code on the UI thread to display the advancement of your background task.
It has no importance for the execution of the call to ProgressChanged.
If you need to communicate some different data to your ProgressChanged event you could use the overload of ReportProgress that takes two arguments and allows to pass the instance of a custom object as second parameter.
In this very trivial example, I have defined a class named WorkingStatus with just one property that I change in the DoWork method, then I pass an instance of this class to the ProgressChanged event. Of course your WorkingStatus class could be more complex with all the informations that you want to display on the UI thread
public class WorkingStatus
public Current as Integer
'.... other properties as needed....
End Class
Sub Main
Dim bkw = new BackgroundWorker()
bkw.WorkerReportsProgress = true
AddHandler bkw.ProgressChanged, AddressOf bgw_ProgressChanged
AddHandler bkw.DoWork, AddressOf bgw_DoWork
bkw.RunWorkerAsync()
' This loop just to avoid the immediate close of the example
Dim counter = 0
While (bkw.IsBusy)
counter+=1
Console.WriteLine("IsBusy " & counter.ToString())
Thread.Sleep(150)
End While
End Sub
private sub bgw_DoWork(sender as object, e as DoWorkEventArgs)
Dim bgw = DirectCast(sender, BackgroundWorker)
Dim sts = new WorkingStatus() With {.Current = 0}
' A simulation of your inner working
for i = 0 to 10
Thread.Sleep(5000)
sts.Current+=1
bgw.ReportProgress(1, sts)
Next
Console.WriteLine("Background DoWork ENDED")
End Sub
private sub bgw_ProgressChanged(sender as object, e as ProgressChangedEventArgs)
Dim sts = DirectCast(e.UserState, WorkingStatus)
Console.WriteLine("Progress:" & e.ProgressPercentage.ToString() & ", Status=" & sts.Current)
End Sub

How to cancel a function which is called by a background worker?

I have a background worker which calls a function within a separate class. This process may be required to be canceled at any time via a button click from the front end. I have tried using CancelAsync() but this has no effect. The cofunds_downloadfiles is the function which i am calling. How do i go about canceling the process?
TIA.
Private Sub btnProcessdld_Click(sender As System.Object, e As System.EventArgs) Handles btnProcessdld.Click
Dim cfHelper As New CoFundsHelper
If btnProcessdld.Text = "Process" Then
btnProcessdld.Text = "Cancel"
If chkDailyFiles.Checked = False And chkWeeklyFiles.Checked = False Then
MessageBox.Show("Please select which files you want to download")
Else
lblProgress.Text = "Processing...if you are downloading weekly files this may take a few minutes"
uaWaitdld.AnimationEnabled = True
uaWaitdld.AnimationSpeed = 50
uaWaitdld.MarqueeAnimationStyle = MarqueeAnimationStyle.Continuous
uaWaitdld.MarqueeMarkerWidth = 60
_backGroundWorkerdld = New BackgroundWorker
_backGroundWorkerdld.WorkerSupportsCancellation = True
_backGroundWorkerdld.RunWorkerAsync()
End If
ElseIf btnProcessdld.Text = "Cancel" Then
btnProcessdld.Text = "Process"
_backGroundWorkerdld.CancelAsync()
uaWaitdld.AnimationEnabled = False
End If
Private Sub StartProcessdld(ByVal sender As Object, _
ByVal e As System.ComponentModel.DoWorkEventArgs) Handles _backGroundWorkerdld.DoWork
Dim cfHelper As New CoFundsHelper
cfHelper.ConnString = PremiumConnectionString
Dim dateValue As String
Dim weekly As Boolean = False
Dim daily As Boolean = False
If dtePicker.Value IsNot Nothing Then
dateValue = Format(dtePicker.Value, "yyyyMMdd")
If chkWeeklyFiles.Checked = True Then
weekly = True
End If
If chkDailyFiles.Checked = True Then
daily = True
End If
cfHelper.Cofunds_DownloadFiles(dateValue, weekly, daily)
Else
Throw (New Exception("Date Field is empty"))
End If
End Sub
Basically you can do the following:
in the DoWork sub, test for cancellationpending property
if it's true then you simply do not call that function and maybe put e.Cancelled = true then check this in the RunWorkerCompleted and decide what you have to do.
if you need to cancel it, simply make a Stop() sub in your class that does exactly that - stops the procedure. Then, you simply need to invoke it like
Me.Invoke(Sub()
myClass.Stop()
End Sub)
you may need to suspend the background worker until the call from your main thread has returned. You do this using semaphores: Private chk As New Semaphore(1,1,"checking1") You put this as a global variable to both your main thread and the background worker.
in the backgroundworker_doWork you use the semaphore like chk.WaitOne() AFTER the line that need to execute.
in the method of your class, when it has finished with computing you put a.Release
The semaphore is only needed if you need to make sure you wait for a result. It kind of defeats the purpose of multithreading but you can perform other actions in the worker before waiting for the main thread (like starting another thread with something else etc).
Other than that invoking a stop method should be enough. Sorry that i haven't had time to analyze your code but i hope i put you in the right direction.
CancelAsync doesn't actually do the cancelling of the worker (is just sets CancellationPending = True) so you basically have to check the state of the BackGroundWorker in your function:
Do While Not worker.CancellationPending
'some long running process
Loop
I have found that this is not 100% reliable however, so it may be safer to use a cancelled flag of your own.

Why does my progress bar disappear?

I have seen this problem before but I haven't seen an answer to the question that applied to my particular case. I have a BackgroundWorker running in my VB form, as well as a progress bar and some labels. I also (if it's important) have a WebBrowser on my form, but it isn't affected by the thread.
Private Sub BackgroundWorker1_DoWork(ByVal sender As System.Object, ByVal e As System.ComponentModel.DoWorkEventArgs) Handles BackgroundWorker1.DoWork
Dim ints As Integer = Int(InputBox("What number to start at?"))
Dim inte As Integer = Int(InputBox("What number to end at?"))
ToolStripStatusLabel1.Text = "0 / " & inte - ints
ToolStripProgressBar1.Maximum = inte
ToolStripProgressBar1.Minimum = ints
ToolStripProgressBar1.Style = ProgressBarStyle.Continuous
Try
For z As Integer = ints To inte
ToolStripProgressBar1.Value = z
ToolStripStatusLabel1.Text = z & "/" & inte
'do some stuff here
catch etc
next
When the loop is running, sometimes it stops and the progress bar disappears. Any idea why?
Btw the only thing I'm doing in there is running an httpwebrequest and handling the string.
This is likely to do with the fact that you're setting the value of a user interface object (ToolStripProgressBar1) within the BackgroundWorker's DoWork method which is running in it's own thread, separate from the User Interface thread which the ToolStripProgressBar1 is in.
As per the Note on this MSDN page:
You must be careful not to manipulate any user-interface objects in
your DoWork event handler. Instead, communicate to the user interface
through the ProgressChanged and RunWorkerCompleted events.
BackgroundWorker events are not marshaled across AppDomain boundaries.
Do not use a BackgroundWorker component to perform multithreaded
operations in more than one AppDomain.
What you should do is to change the code that's inside the loop (For z As Integer = ints To inte) so that instead of setting the Value and Text properties directly, you call the BackgroundWorker's ReportProgress method. This raises the ProgressChanged event which you can then handle on the main UI thread. It's in here that you can then safely access the properties of User Interface components and objects.