Background worker problem [duplicate] - vb.net

This question already has answers here:
Closed 11 years ago.
Possible Duplicate:
Cross-thread operation not valid
Hi,
I am testing a background workder. I am running the following code for the test.
Private Sub bgwTest_DoWork(ByVal sender As System.Object, ByVal e As System.ComponentModel.DoWorkEventArgs) Handles bgwTest.DoWork
Dim a As Integer = 0
Do While a < 10 'Infinite loop
ComboBox1.Items.Add(1)
Loop
End Sub
Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
PictureBox1.Visible = True 'Contains my animated GIF
'Purpose is GIF keeps looping (it is animated GIF) despite computer is stuck in infinite loop
bgwTest.RunWorkerAsync()
End Sub
But this code generates the following error:
"Cross-thread operation not valid: Control 'ComboBox1' accessed from a thread other than the thread it was created on."
Please help.
Thanks
Furqan

You should not modify GUI elements on non-GUI threads. All modifications to textboxes, comboboxes, labels, etc.. should be done on the main thread. In the case of a BackgroundWorker that would be inside the RunWorkerCompleted and ProgressChanged events.
So inside the DoWork event you could fill some data structure (a list for example) with calculated values and in the RunWorkerCompleted event read this structure and update the combobox.

You cannot access controls from another thread, you need to use delegates to achieve this.
Further reading on the matter is here:
http://msdn.microsoft.com/en-us/library/ms171728.aspx

Related

An error occurred creating the form (activex control/worker thread)

I am having an issue using an external component which can be found here:
https://www.codeproject.com/Articles/27738/NET-component-that-simplifies-tracking-of-system
The problem I am having is that I want to talk to the main thread (frmMain), but when this takes place SystemIdleTimer1_OnEnterIdleState wants to create a new form because of SystemIdleTimer1 is a different thread.
Private Sub SystemIdleTimer1_OnEnterIdleState(ByVal sender As System.Object, ByVal e As EdinDazdarevic.IdleEventArgs) Handles SystemIdleTimer1.OnEnterIdleState
frmMain.stopActiveTimer()
MessageBox.Show("Entered idle state")
End Sub
How can I call data in the main thread in the current scenario?
There are some similar questions on StackOverflow, but I am struggling to tie everything together:
Crossthread operation not valid... - VB.NET
How do I call a function on the main thread from another thread in vb

Visual Basic Handles and Multiple Variables with same name [duplicate]

This question already has answers here:
How can I make a single event handler handle ALL Button.Click events?
(3 answers)
Closed 6 years ago.
I've made a program in Visual Basic to open files by clicking in a PictureBox, but as a result, I've ended up with 36 pictureboxes, and it's being a pain to handle every one of them, as the only way I've discovered to reference the PictureBox(NÂș)
was making
Dim pictureBoxes(35) As PictureBox
and setting each one as following:
pictureBoxes(0) = PictureBox1
pictureBoxes(1) = PictureBox2
pictureBoxes(2) = PictureBox3
...
pictureBoxes(35) = PictureBox36
Then I could use it in a For Loop:
PictureBoxes(i).Image = iconForFile.ToBitmap()
So, my question is, is there a way to make it easier to reference, instead of doing that way?
The same thing is kinda making me wonder in the Handles part of the code:
Private Sub PictureBox1_Click(sender As Object, e As EventArgs) Handles PictureBox1.Click, PictureBox2.Click, PictureBox3.Click, PictureBox4.Click, PictureBox5.Click, PictureBox6.Click, PictureBox7.Click, PictureBox8.Click, PictureBox9.Click, PictureBox10.Click, PictureBox11.Click, PictureBox12.Click, PictureBox13.Click...
Any light would be very helpful, thanks!
I don't know how your controls are structured on the form, but if they are part of the same collection, a flowpanel for example, you can get all of the controls of a certain type. In your case you want to be able to access individual boxes so you can do this once and store the boxes in the list as you are already doing.
Dim pictureBoxes = FlowLayoutPanel.Controls.OfType(Of PictureBox)().Tolist()
For Each picture in pictureBoxes
AddHandler picture.Click, AddressOf PictureBox_Click
Next

How to properly interact with UI in threading in 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

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.

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.