How to properly interact with UI in threading in VB.NET - vb.net

I'm new to VB.NET threading
As for simple testing I tried the following, which I need to smoothly fill a listbox with values.
But it does not work as I expect, it hangs the interface. Please let me know what I'm doing wrong here.
Thank you.
Imports System.Threading
Public Class Form1
Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
Dim Thr As Threading.Thread
Thr = New Threading.Thread(New Threading.ThreadStart(AddressOf tprocess))
'Thr.SetApartmentState(ApartmentState.STA)
Thr.IsBackground = True
Thr.Start()
End Sub
Private Delegate Sub DoStuffDelegate()
Private Sub tprocess()
Dim i As Integer
For i = 0 To 20000
If Me.InvokeRequired Then
Me.Invoke(New DoStuffDelegate(AddressOf tprocess))
Else
ListBox1.Items.Add(i)
End If
Next
End Sub
End Class

When you write code to create a thread then you always have to worry about the kind of bugs that threading can cause. They are very hard to diagnose, the only decent way to address them is to know they exist and to write the code carefully so you know how to avoid them.
The most common threading bugs are threading races, deadlock and firehose bugs. You have the 1st and the 3rd bug in your code. You are complaining about the 3rd. Very quickly: the threading race bug is using Me.InvokeRequired. You have no guarantee that it is still true when the Me.Invoke() statement executes. This goes wrong when the user closes the window while your thread is still running. When you try to fix this problem you'll get to see what the 2nd bug looks like. But you are not there yet.
The firehose bug is the Me.Invoke() call. Very fast, takes less than a microsecond of work for the worker thread, you do it 20000 times at a very high rate. It is however another thread that must actually do the work of adding the item, your UI thread. That is not fast, it not only has to add the item but it also needs to repaint the control. Many microseconds.
While this goes on, your UI thread is burning 100% core, trying to keep up with the relentless rate of invoke requests. Working as hard as it can to add items to the listbox. Something has to give, while it is doing this it is not taking care of the lower priority jobs it has to do. Painting and responding to user input. In effect, your UI looks completely frozen. You can't see it paint anymore and trying to, say, close the window doesn't work. It isn't actually dead, it is hard at work.
Takes a while, probably a few handful of seconds, give or take. Until the worker thread finishes its for() loop and stops slamming the UI thread with invoke requests. And everything turns back to normal.
A firehose bug like this is pretty fundamental. The only way to fix it is to call Invoke() less often or at a lower rate. Note how putting Thread.Sleep(50) after the Invoke() call instantly fixes it. But of course that slows down your worker thread a lot. You call Invoke() less often by using AddRange() instead of Add(), adding (say) 1000 items at a time. Which is the proper fix but now it becomes fairly pointless to still try to update the listbox from the worker thread. Might as well do it with a single AddRange() call. The quickest way.

Try changing:
Thr = New Threading.Thread(New Threading.ThreadStart(AddressOf tprocess))
to this:
Thr = New Threading.Thread(AddressOf tprocess)
ThreadStart will start that thread immediately

I tried the following way. It's almost easy for me to handle. Backgroudworker manages this situation perfectly well.
Private Sub BackgroundWorker1_DoWork(ByVal sender As System.Object, ByVal e As System.ComponentModel.DoWorkEventArgs) Handles BackgroundWorker1.DoWork
Dim i As Integer
For i = 1 To 20000
BackgroundWorker1.ReportProgress((i / 20000) * 100, i)
Threading.Thread.Sleep(1)
Next
End Sub
Private Sub BackgroundWorker1_ProgressChanged(ByVal sender As Object, ByVal e As System.ComponentModel.ProgressChangedEventArgs) Handles BackgroundWorker1.ProgressChanged
ProgressBar1.Value = e.ProgressPercentage
ListBox1.Items.Add(e.UserState)
End Sub
Private Sub BackgroundWorker1_RunWorkerCompleted(ByVal sender As Object, ByVal e As System.ComponentModel.RunWorkerCompletedEventArgs) Handles BackgroundWorker1.RunWorkerCompleted
MsgBox("Complete")
End Sub

Related

Stop a background worker

I am trying to add a STOP button to my program to, stop a background worker. I have had no luck doing it with the following.
This is my button event
Private Sub GOButton_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles GOButton.Click
If BackgroundWorker1.IsBusy Then
Exit Sub
Else
PullIPs()
End If
End If
End Sub
The PullIPs sub does alot of stuff, and at the end, starts the backgroundworker
BackgroundWorker1.RunWorkerAsync()
Backgroundworker1 kicks off another sub, like so
Private Sub BackgroundWorker1_DoWork(ByVal sender As System.Object, ByVal e As System.ComponentModel.DoWorkEventArgs) Handles BackgroundWorker1.DoWork
BackgroundWorker1.ReportProgress(50)
PingAll()
End Sub
So, after that maze, I would like a way to stop the backgroundworker mid 'PingAll()'.
Lastly,
Private Sub StopButton_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles StopButton.Click
BackgroundWorker1.CancelAsync()
End Sub
Note: SupportCancelation IS enabled. I have also looked all over the place, and it looks like I am doing it, how it should work...
It appears that you have looked all over the place except for the one obvious place that you should have looked first, i.e. the Help documentation. The doco for the BackgroundWorker.CancelAsync method has this to say:
CancelAsync submits a request to terminate the pending background
operation and sets the CancellationPending property to true.
When you call CancelAsync, your worker method has an opportunity to
stop its execution and exit. The worker code should periodically check
the CancellationPending property to see if it has been set to true.
Where in your code are you doing as that instructs? Nowhere, so you're obviously not doing it how it should work.
Calling CancelAsync only requests a cancellation. It's still up to you to add code to your DoWork event handler to test whether a cancellation has been requested and stop doing the work if it has. The DoWork event handler can do anything at all so calling CancelAsync is not going to simply abort that on the spot without any consideration for what state the app is in and whether any cleanup may be required.
You know what work is being done so it's up to you write the code such that that work can be cancelled part way through. As it is, all you're doing is a single call to PingAll so there is no way to cancel it. You need to restructure that code, e.g. turn it into a loop that does one ping per iteration and then you can cancel between iterations if required.

How can I get a program to run automatically at specific times a day (VB.NET)

The application I'm developing right now allows the user to update an Excel sheet or Sql database for set metrics twice a day. The program does this by popping up at certain times (e.g. 6:00 AM, 5:00 PM, 3:42 PM, whatever the user sets). By having the program pop up at certain times, the program ("Auto Excel It!!!") allows you as the user to track set data (say, sales calls, sales presentations, meetings, number of hours coding, number of jalepeƱo burritos eaten, etc.).
How can a developer get this program to "pop up"/start/function automatically at specific times through the means of the Windows Scheduler API (or something better)?
Here's how my understanding's evolved lately:
Nothing --> Use Timers As The Program Runs In The Background --> Use Windows Scheduler's API To Run Automatically (Current) --> Possible New Understanding From Your Answer
For example, I'm aware of: DispatcherTimers, Timers, another timer I'm not aware of, Sleep(), Windows Scheduler. But with these in mind, I don't know what to do regarding the following: Automatically starting a program via Windows Scheduler; Preserving computer resources if a timer is used; or even how to get this top pop up automatically.
Update 1:
#nfell2009:Your logic helped me out big time. At first I had to toy around with converting your Timer here to a DispatcherTimer (WPF forms standard, it seems). Then, I switched the the "Handles" for the Sub tCheckTime to "AddHandler tCheckTime.Tick, AddressOf tCheckTime_Tick" --- Why I had to do this is a good question.
Then, once I had the basic EventHandlers set up, your idea for comparing the user's text (As Date) to the System.Date is good--When I screwed something up and couldn't get the code to work, I switched it up and converted System.Date to a String--i.e. I went from String->Date To Date->String... That's when I got the Timer to work. When my System.Time ticked to 3:12 PM, the MsgBox popped up with "Your Message Here."
(A Quick (Evil) Thank You! I've spent four-plus hours getting this to work)
Code:
From Using "Handles" At tCheckTime_Tick (Which seems like it 'should' work)
Private Sub tCheckTime_Tick(sender As Object, e As EventArgs) Handles tCheckTime.Tick
...
End Sub
To AddHandler blah, AddressOf tCheckTime_Tick (Does work)
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Loaded
'MsgBox(Now().ToString("hh:mm")) 'String.Format("{hh:mm}", Now()))
AddHandler tCheckTime.Tick, AddressOf tCheckTime_Tick 'Why is this necessary?
tCheckTime.Interval = New TimeSpan(0, 1, 0)
End Sub
Public Class Form1
Dim iSetTime As Date
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
End Sub
Private Sub btnSetTime_Click(sender As Object, e As EventArgs) Handles btnSetTime.Click
If (tCheckTime.Enabled = True) Then
tCheckTime.Enabled = False
End If
iSetTime = txtTHour.Text + ":" + txtTMinute.Text + ":" + txtTSecond.Text
tCheckTime.Enabled = True
End Sub
Private Sub tCheckTime_Tick(sender As Object, e As EventArgs) Handles tCheckTime.Tick
If (TimeOfDay = iSetTime) Then
MsgBox("Your Message")
End If
End Sub
End Class
You will need error checking for the textboxs, but its simply:
3 textboxs with indication of which is which, so maybe a label each with H, M, S - or something.
A button which will set time and a timer. Naming:
Textboxs
Hours = txtTHour
Minutes = txtTMinute
Seconds = txtTSecond
Buttons
Start Button = btnSetTime
Timers
Timer = tCheckTime
I can think of two easy ways:
Have your program calculate the time until it should next appear in seconds and then set a timer with an elapsed time such that when the tick event is raised you can do whatever you need to do.
Use MS Task Manager to launch your program when and as needed.

Public Labels(Pointers)

I am working with VB 2010 for an application which requires me to go back to a sub. After some digging, I found out GoSub is no longer supported. I tried using Goto but apparently you can't use it outside the sub the label is in. I tried calling the sub put the parameters were not known to me.
Private Sub BackgroundWorker1_DoWork(ByVal sender As Object, ByVal e As System.ComponentModel.DoWorkEventArgs) Handles BackgroundWorker1.DoWork
I do not want to use the RunWorkerAsync because the worker will already be working. Please advise me on this matter.
It sounds like you are trying to solve a problem using procedural logic with an object-oriented tool.
If I understand you correctly, the sub is calling another section of code, and needs to return to the sub after it's finished. Make the called code itself a function or sub, and call it from the primary sub routine.

This BackgroundWorker is currently busy and cannot run multiple tasks concurrently

I am confused. Yes i understand I can't use the same backgroundworker to do two tasks at the same time. What I do not understand is this. Here is my code (all this thing does is set the marqueeanimationspeed of a progress bar...
'THE FOLLOWING SUB TOGGLES THE PROGRESS BAR
Private Sub BackgroundWorker1_DoWork(ByVal sender As System.Object, ByVal e As System.ComponentModel.DoWorkEventArgs) Handles BackgroundWorker1.DoWork
'CHECK THE STATE OF THE PROGRESS BAR AND TOGGLE IT
If ToolStripProgressBar1.MarqueeAnimationSpeed = 0 Then
ToolStripProgressBar1.MarqueeAnimationSpeed = 22
End If
ToolStripProgressBar1.MarqueeAnimationSpeed = 0
End Sub
OK, so how long can this possibly take? Doesn't the worker do the task and exit? So I put in a pause (system.threading.thread.sleep(2000)... same problem, made it 20 seconds... same problem.
So I am assuming this is a simple thing I'm missing, but I've spent more than an hour searching and I don't get it.
All I am trying to accomplish here is to start the marquee progress bar while the UI is running something else, and then stop it. I assume I can create another backgroundworker and just use it, but I want to understand why the first one is not done with the task.
Thanks, and again, yes I spent an hour searching and I find all kinds of "solutions" but no explanation as to why this thing is not finished.
OK SO HERE IS THE SUB CALLING THE BGW
'THE FOLLOWING SUB FIRES THE SETTING CONNECTION STRINGS SUB
Private Sub SetCSButton_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles SetCSButton.Click
'START THE PROGRESS BAR & CHANGE THE LABEL
BackgroundWorker1.RunWorkerAsync()
Threading.Thread.Sleep(1000)
ToolStripStatusLabel1.Text = "Preparing the connection strings..."
Me.Refresh()
thread3 = New System.Threading.Thread(AddressOf SetConnectionStrings)
thread3.Start()
'STOP THE PROGRESS BAR & CHANGE THE LABEL
BackgroundWorker2.RunWorkerAsync()
Threading.Thread.Sleep(1000)
ToolStripStatusLabel1.Text = "Standing by..."
Me.Refresh()
End Sub**strong text**
I had a 20second delay but still the first BGW does not finish. I know this is something simple but I dont understand, that's all I am after here.
I DID change the code and do not use the same methodology as I was trying at the time I wrote this question... What I do not understand is why a simple operation is never, apparently, finishing... having said that, it DOES finish as I was able to show a msgbox using the runworkercompleted event. So, as I tried and failed to convey, thbis is not about the right or wrong way to code, I know it wa wrong and was just trying to be quick and dirty, regardless of that, I am not doing that now, but I do not understand why the BGW is "still working". There must be some simple thing I am ignorant about.
Thanks
The error is not in the posted code but where you start the Bgw.
But it is all irrelevant because you should not touch the GUI from DoWork:
Private Sub BackgroundWorker1_DoWork(...) Handles BackgroundWorker1.DoWork
'CHECK THE STATE OF THE PROGRESS BAR AND TOGGLE IT
If ToolStripProgressBar1.MarqueeAnimationSpeed = 0 Then ' Boom, cross-threading violation
ToolStripProgressBar1.MarqueeAnimationSpeed = 22
End If
I don't think you need a Bgw, thread or timer here. Just change the speed before/after the slow action.

How to dynamically create a background worker in VB.net

I have a VB.net project which uses a background worker to do some stuff.
Now I want to expand the project to be able to do multiple stuff :)
A user can enter an URL in a textbox and when the user click on the parse button the program creates a new tabcontrol a outputs some data.
I use a hardcoded background worker for this.
But now I want to run multiple background workers to do this stuff so I can't rely on hard coding the background worker(s).
Is it possible to create background workers dynamically.
I just don't have any idea how to set this up since I think I need to set up the different methods and variables like:
Private bw As BackgroundWorker = New BackgroundWorker
bw.WorkerReportsProgress = True
bw.WorkerSupportsCancellation = True
AddHandler bw.DoWork, AddressOf bw_DoWork
AddHandler bw.ProgressChanged, AddressOf bw_ProgressChanged
AddHandler bw.RunWorkerCompleted, AddressOf bw_RunWorkerCompleted
bw.RunWorkerAsync()
Private Sub bw_DoWork(), Private Sub bw_RunWorkerCompleted() and Private Sub bw_ProgressChanged()
I think I need to declare the background workers in some sort of array like variable (list / dictionary)??? Other then that I have no idea how to tackle this.
Here is how
Public Class Form
Private Workers() As BackgroundWorker
Private NumWorkers = 0
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
NumWorkers = NumWorkers + 1
ReDim Workers(NumWorkers)
Workers(NumWorkers) = New BackgroundWorker
Workers(NumWorkers).WorkerReportsProgress = True
Workers(NumWorkers).WorkerSupportsCancellation = True
AddHandler Workers(NumWorkers).DoWork, AddressOf WorkerDoWork
AddHandler Workers(NumWorkers).ProgressChanged, AddressOf WorkerProgressChanged
AddHandler Workers(NumWorkers).RunWorkerCompleted, AddressOf WorkerCompleted
Workers(NumWorkers).RunWorkerAsync()
End Sub
End Class
Then the handlers
Private Sub WorkerDoWork(sender As Object, e As System.ComponentModel.DoWorkEventArgs)
' Do some work
End Sub
Private Sub WorkerProgressChanged(sender As Object, e As System.ComponentModel.ProgressChangedEventArgs)
' I did something!
End Sub
Private Sub WorkerCompleted(sender As Object, e As System.ComponentModel.RunWorkerCompletedEventArgs)
' I'm done!
End Sub
Imagine multithreading could be so easy. This works great unless you have 1000's of workers. The sender argument in each handler can be used to check which worker is reporting progress etc..
Although BackgroundWorkers can be the best, simplest, and smartest way to multithread sometimes, I think you might now look to use one of the other ways to multithread.
There are lots of debates/arguments/trolling regarding which methods are the best to use in each circumstance, so my advice to you would be to have a quick look at the following articles and decide for yourself (or if you can't find good enough resources to make a decision, ask on SO of course).
You've obviously looked at back ground workers already so I won't list them, nor will I list all the ways you can thread, just a couple that might be of interest to you.
First off, check out the ThreadPool. It's easy to use, and it makes fairly good use of recycling/re-using resources. There are some cons such as using/holding too many threads from a pool can exhuast the pool, but in simple applications that shouldn't be an issue.
There is also the CLR Async model which is supported across a suprising amount of the framework itself, particularly in cases involving some form of IO resource (file, network, etc).
Another approach is the Parallel Class which is one of my favourites - I've been hooked on multiline lambda since it was introduced and parallel provides a good platform for doing so.
In all of the above cases, you can create tertiary threads on the fly, without having to create and maintain a pool of background workers yourself. It's hard to say which approach would work best for you from the information provided, but personally, I'd consider the threadpool if retrieval of the data to populate your tabs doesn't take too long.
Hope that helps!
If I understand your question well, try declaring a BackgroundWorker in class level as:
Friend WithEvents bgw1 As BackgroundWorker
then in class constructor instantiate it.
Public Sub New()
bgw1 = New BackgroundWorker
End Sub
In class level declaration choose bgw1 and its event dropdown section choose DoWork, ProgressChanged and RunWorkerCompleted Events.