I'm new with multithread and backgroundworker.
I'm tried to write a simple test based upon tutorials and forum threads, but it is not working how I expected.
I have a windows form appilcation with Button1, Label1, BackgroundWorker1 controls.
I'd like to do that when I click on the button:
Backgroundworker check continously if a variable state True or False
First change label text to False (first time state = false)
if state is set True in the main thread, change label text to True (state is set True when the "Sample process" (For Next) ends)
My code is:
Public state As Boolean
Public Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
Debug.Print("Button1_Click ThreadID - " & Thread.CurrentThread.ManagedThreadId)
state = False
BackgroundWorker1.RunWorkerAsync()
'--Sample process in the main thread
For c = 0 To 10000
Debug.Print(c)
Next
state = True
End Sub
Dim c As Integer
Public Sub BackgroundWorker1_DoWork(sender As Object, e As System.ComponentModel.DoWorkEventArgs) Handles BackgroundWorker1.DoWork
setlabel(state.ToString)
Do While state = False
Debug.Print("BackgroundWorker1_DoWork ThreadID - " & Thread.CurrentThread.ManagedThreadId & " - state: " & state)
Thread.Sleep(100)
Loop
setlabel(state.ToString)
End Sub
Public Sub BackgroundWorker1_RunWorkerCompleted(sender As Object, e As RunWorkerCompletedEventArgs) Handles BackgroundWorker1.RunWorkerCompleted
Debug.Print("BackgroundWorker1_RunWorkerCompleted - State: " & state)
End Sub
Sub setlabel(text As String)
Label1.Invoke(Sub()
Label1.Text = text
End Sub)
End Sub
My problem is that:
Label's text doesn't change to False at the begining of
BackgroundWorker1_DoWork
in the Do While there is no Debug.Print
The output is:
Button1_Click ThreadID - 9
0
1
2
...
9998
9999
10000
BackgroundWorker1_RunWorkerCompleted - State: True
What do I wrong?
You are tying up the UI thread with the For loop:
For c = 0 To 10000
Debug.Print(c)
Next
You need to let the UI thread go idle for the call to setlabel(state.ToString)
to actually update the UI.
So you need to have something like a timer (or even a second background worker) wait a period of time before setting state = True.
Related
I have a background worker that is supposed to be updating a ToolStripLabel with some status messages. However, the updating is not happening, but no errors are being thrown. Here's the code I am using:
Private Sub BackgroundWorker3_DoWork(sender As Object, e As DoWorkEventArgs) Handles BackgroundWorker3.DoWork
BackgroundWorker3.WorkerReportsProgress = True
Dim Counter As Integer = 0
Do Until BW1Running = False
Counter = Counter + 1
Threading.Thread.Sleep(1000)
Incident_Form.BackgroundWorker3.ReportProgress(Counter)
If Counter >= 100 Then
e.Result = False
Return
End If
Loop
If BW1Running = False Then
Counter = 100
Incident_Form.BackgroundWorker3.ReportProgress(Counter)
End If
End Sub
Private Sub BackgroundWorker3_ProgressChanged(sender As Object, e As ProgressChangedEventArgs) Handles BackgroundWorker3.ProgressChanged
Me.ToolStripStatusLabel1.Text = e.ProgressPercentage.ToString
End Sub
Nothing happens when the ProgressChanged is fired. I've debugged it and it'll print a line to the output window, but it will not update that label. Any ideas on what I'm missing?
You're calling:
Incident_Form.BackgroundWorker3.ReportProgress()
instead of just:
BackgroundWorker3.ReportProgress()
Your BackgroundWorker3_ProgressChanged method is subscribed to the ProgressChanged event of the BackgroundWorker located in the current form, not in the Incident_Form form.
Remove Incident_Form from the beginning of the BackgroundWorker3.ReportProgress() calls and you should be good to go.
I'm trying to create a thread so when I click a button it creates a new PictureBox from a class, this is how far I've got but nothing comes up on the screen at all.
Form1 code:
Public Class Form1
Private pgClass As New SecondUIClass
Private Sub Button1_Click(ByVal sender As System.Object, _
ByVal e As System.EventArgs) Handles Button1.Click
pgClass = New SecondUIClass
pgClass.x += 100
pgClass.thread()
End Sub
End Class
Class Code:
Imports System.Threading
Public Class SecondUIClass
Public Const count As Integer = 1000
Public emeny(count - 1) As PictureBox
Public counter As Integer = 0
Public x As Integer = 0
Private trd As Thread
Public Sub thread()
trd = New Thread(AddressOf NewUIThread)
trd.SetApartmentState(ApartmentState.STA)
trd.IsBackground = False
trd.Start()
End Sub
Private Sub NewUIThread()
emeny(counter) = New PictureBox
emeny(counter).BackColor = Color.Red
emeny(counter).Visible = True
emeny(counter).Location = New System.Drawing.Point(x, 100)
emeny(counter).Size = New System.Drawing.Size(10, 50)
Form1.Controls.Add(emeny(counter))
For z = 0 To 13
emeny(counter).Location = New Point(emeny(counter).Location.X + 10, emeny(counter).Location.Y)
Application.DoEvents()
Threading.Thread.Sleep(100)
Next
counter += 1
End Sub
End Class
I have posted something similar before on here but it was different, the pictureBoxes were showing on the screen but I was trying to get them to move at the same time but they wouldn't move, they only moved one at a time. The question that I asked before was this Multi threading classes not working correctly
I made a few assumptions for this answer so it may not work for you out of the box but I think it will put you on the right track without using any Thread.Sleep calls because I personally don't like building intentional slows to my apps but that's a personal preference really.
So For my example I just used a bunch of textboxes because I didn't have any pictures handy to fiddle with. But basically to get it so that the user can still interact with the program while the moving is happening I used a background worker thread that is started by the user and once its started it moves the textboxes down the form until the user tells it to stop or it hits an arbitrary boundary that I made up. So in theory the start would be the space bar in your app and my stop would be adding another control to the collection. For your stuff you will want to lock the collection before you add anything and while you are updating the positions but that is up to your discretion.
So the meat and potatoes:
in the designer of the form I had three buttons, btnGo, btnStop and btnReset. The code below handles the click event on those buttons so you will need to create those before this will work.
Public Class Move_Test
'Flag to tell the program whether to continue or to stop the textboxes where they are at that moment.
Private blnStop As Boolean = False
'Worker to do all the calculations in the background
Private WithEvents bgWorker As System.ComponentModel.BackgroundWorker
'Controls to be moved.
Private lstTextBoxes As List(Of TextBox)
'Dictionary to hold the y positions of the textboxes.
Private dtnPositions As Dictionary(Of Integer, Integer)
Public Sub New()
' Default code. Must be present for VB.NET forms when overwriting the default constructor.
InitializeComponent()
' Here I instantiate all the pieces. The background worker to do the adjustments to the position collection, the list of textboxes to be placed and moved around the form
' and the dictionary of positions to be used by the background worker thread and UI thread to move the textboxes(because in VB.NET you can not adjust controls created on the UI thread from a background thread.
bgWorker = New System.ComponentModel.BackgroundWorker()
Me.lstTextBoxes = New List(Of TextBox)
Me.dtnPositions = New Dictionary(Of Integer, Integer)
For i As Integer = 0 To 10
Dim t As New TextBox()
t.Name = "txt" & i
t.Text = "Textbox " & i
'I used the tag to hold the ID of the textbox that coorelated to the correct position in the dictionary,
' technically you could use the same position for all of them for this example but if you want to make the things move at different speeds
' you will need to keep track of each individually and this would allow you to do it.
t.Tag = i
dtnPositions.Add(i, 10)
'Dynamically position the controls on the form, I used 9 textboxes so i spaced them evenly across the form(divide by 10 to account for the width of the 9th text box).
t.Location = New System.Drawing.Point(((Me.Size.Width / 10) * i) + 10, dtnPositions(i))
Me.lstTextBoxes.Add(t)
Next
'This just adds the controls to the form dynamically
For Each r In Me.lstTextBoxes
Me.Controls.Add(r)
Next
End Sub
Private Sub Move_Test_Load(sender As Object, e As EventArgs) Handles MyBase.Load
Try
'Don't need to do anything here. Placeholder
Catch ex As Exception
MessageBox.Show("Error: " & ex.Message)
End Try
End Sub
Private Sub btnGo_Click(sender As Object, e As EventArgs) Handles btnGo.Click
Try
If Not bgWorker.IsBusy Then
'User starts the movement.
bgWorker.RunWorkerAsync()
End If
Catch ex As Exception
MessageBox.Show("Error: " & ex.Message)
End Try
End Sub
Private Sub btnReset_Click(sender As Object, e As EventArgs) Handles btnReset.Click
Try
'Reset the positions and everything else on the form for the next time through
' I don't set the blnStop value to true in here because it looked cooler to keep reseting the textboxes
' and have them jump to the top of the form and keep scrolling on their own...
For Each r In Me.lstTextBoxes
r.Location = New System.Drawing.Point(r.Location.X, 10)
Next
For i As Integer = 0 To dtnPositions.Count - 1
dtnPositions(i) = 10
Next
Catch ex As Exception
MessageBox.Show("Error: " & ex.Message)
End Try
End Sub
Private Sub bgWorker_DoWork(sender As Object, e As System.ComponentModel.DoWorkEventArgs) Handles bgWorker.DoWork
Try
'This is where we do all the work.
' For this test app all its doing is scrolling through each value in the dictionary and incrementing the value
' You could make the dictionary hold a custom class and have them throttle themselves using variables on the class(or maybe travel at an angle?)
For i As Integer = 0 To dtnPositions.Count - 1
dtnPositions(i) += 1
Next
Catch ex As Exception
blnStop = True
End Try
End Sub
Private Sub bgWorker_RunWorkerCompleted(sender As Object, e As System.ComponentModel.RunWorkerCompletedEventArgs) Handles bgWorker.RunWorkerCompleted
Try
'Once the background worker is done updating the positions this function scrolls through the textboxes and assigns them their new positions.
' We have to do it in this event because we don't have access to the textboxes on the backgroun thread.
For Each r In Me.lstTextBoxes
r.Location = New System.Drawing.Point(r.Location.X, dtnPositions(CInt(r.Tag)))
Next
'use linq to find any textboxes whose position is beyond the threshhold that signifies they are down far enough.
' I chose the number 100 arbitrarily but it could really be anything.
Dim temp = From r In Me.lstTextBoxes Where r.Location.Y > (Me.Size.Height - 100)
'If we found any textboxes beyond our threshold then we set the top boolean
If temp IsNot Nothing AndAlso temp.Count > 0 Then
Me.blnStop = True
End If
'If we don't want to stop yet we fire off the background worker again and let the code go otherwise we set the stop boolean to false without firing the background worker
' so we will be all set to reset and go again if the user clicks those buttons.
If Not Me.blnStop Then
bgWorker.RunWorkerAsync()
Else
Me.blnStop = False
End If
Catch ex As Exception
MessageBox.Show("Error: " & ex.Message)
End Try
End Sub
Private Sub btnStop_Click(sender As Object, e As EventArgs) Handles btnStop.Click
Try
'The user clicked the stop button so we set the boolean and let the bgWorker_RunWorkerCompleted handle the rest.
Me.blnStop = True
Catch ex As Exception
MessageBox.Show("Error: " & ex.Message)
End Try
End Sub
End Class
Theres a lot of code there but a lot of it is comments and I tried to be as clear as possible so they are probably a little long winded. But you should be able to plop that code on a new form and it would work without any changes. I had the form size quite large (1166 x 633). So I think that's when it works best but any size should work(smaller forms will just be more cluttered).
Let me know if this doesn't work for your application.
This is a problem that is well suited to async/await. Await allows you to pause your code to handle other events for a specific period of time..
Private Async Function Button1_Click(ByVal sender As System.Object, _
ByVal e As System.EventArgs) As Task Handles Button1.Click
pgClass = New SecondUIClass
pgClass.x += 100
await pgClass.NewUIThread()
End Sub
End Class
Class Code:
Imports System.Threading
Public Class SecondUIClass
Public Const count As Integer = 1000
Public emeny(count - 1) As PictureBox
Public counter As Integer = 0
Public x As Integer = 0
Private Async Function NewUIThread() As Task
emeny(counter) = New PictureBox
emeny(counter).BackColor = Color.Red
emeny(counter).Visible = True
emeny(counter).Location = New System.Drawing.Point(x, 100)
emeny(counter).Size = New System.Drawing.Size(10, 50)
Form1.Controls.Add(emeny(counter))
For z = 0 To 13
emeny(counter).Location = New Point(emeny(counter).Location.X + 10, emeny(counter).Location.Y)
await Task.Delay(100) 'The await state machine pauses your code here in a similar way to application.doevents() until the sleep has completed.
Next
counter += 1
End Sub
End Class
hi i want to delay the execution of code for some time on buton click i cal a func named chance() ..
which gets called after a picture box.image change .. bt the image does not change nd func chance() starts ... i want delay in chance() after the picture is changed ... thus help me ..
code
Private Sub p11_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles p11.Click
row = 1
col = 1
chck() 'function which returns hit var value
p11.Enabled = False
If hit = 1 Then
p11.Image = Image.FromFile("G:\visual progs\BATTLESHIP\hit.png")
ElseIf hit = 0 Then
p11.Image = Image.FromFile("G:\visual progs\BATTLESHIP\miss.png")
lblstatus.Text = "COMPUTER's TURN ... PLEASE WAIT ... "
chance() ' func begins
End If
End Sub
Function chance()
***'here i want a pause for 2 sec***
Dim z As Int16 = 1
While z = 1
row = mnw.Next(9) + 1
col = mnw.Next(9) + 1
If c(row, col) = False Then
c(row, col) = True
z = 0
End If
End While
chck1() ' checks for hit or miss for computer
changepic() 'changes pic hit or miss for computer
Return 0
End Function
To get a pause of 2 seconds, you simply need to suspend the thread by doing this:
Thread.Sleep(2000)
Although you can use Thread.Sleep to introduce a delay, it has generally undesirable side-effects, in particular the form and its controls become unresponsive. A better way is to use a timer - that way the form is still responsive (e.g. you can move it around).
Also, you seem to be a little unsure of where to use a Sub and where to use a Function. A Sub does something, and a Function is used to return a value, ideally with no side-effects.
You could try this with a new Windows Forms project and just a PictureBox named p11 and a Label named lblStatus:
Imports System.IO
Public Class Form1
Dim tim As Windows.Forms.Timer
Const GAMEPATH As String = "G:\visual progs\BATTLESHIP\"
Dim HitImgFile As String = Path.Combine(GAMEPATH, "hit.png")
Dim MissImgFile As String = Path.Combine(GAMEPATH, "miss.png")
Private Sub SetUpTimer()
tim = New Timer
tim.Interval = 2000 ' milliseconds
tim.Enabled = False
AddHandler tim.Tick, AddressOf Chance
End Sub
Private Sub Chance(sender As Object, e As EventArgs)
tim.Enabled = False
' your code for the computer's turn goes here
lblStatus.Text = "Your turn"
p11.Enabled = True
End Sub
Private Function IsHitByUser() As Boolean
' placeholder code for the actual check
If Rnd() < 0.5 Then
Return True
End If
Return False
End Function
Private Sub DoComputerTurn()
lblStatus.Text = "COMPUTER's TURN ... PLEASE WAIT ... "
p11.Enabled = False
tim.Enabled = True
End Sub
Private Sub p11_Click(sender As Object, e As EventArgs) Handles p11.Click
If IsHitByUser() Then
p11.Image = Image.FromFile(HitImgFile)
lblStatus.Text = "HIT"
Else
p11.Image = Image.FromFile(MissImgFile)
DoComputerTurn()
End If
End Sub
Private Sub StartGame()
lblStatus.Text = "Your turn"
End Sub
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
SetUpTimer()
StartGame()
End Sub
End Class
I have a form which, at present, is doing nothing but opening. The form has 2x controls - a 'Close' button and a Progress Bar. However, when I open the form, I get nothing. The Progress Bar just sits there doing nothing.
I've tried both Marquee (which I understand may not work in Windows 8) and Continuous, but I can't get anywhere with either.
This is how I'm showing the form when the program starts -
Sub main()
Dim ProgressForm As New formProgress
ProgressForm.ShowDialog()
End Sub
And below are the properties for the Progress Bar. Am I missing something that would get this bar working? Thanks.
Additional Information
For my full program, I did originally try using the Block style for the Progress Bar, but I kept getting the following error when trying to update the Progress Bar from a BackgroundWorker. This is why I am trying to get a simple Marquee/Continuous bar working instead.
Additional information: Cross-thread operation not valid: Control 'proWorking' accessed from a thread other than the thread it was created on.
if you use marquee style you have to set marqueeanimationspeed to some value
//
// progressBar1
//
this.progressBar1.Location = new System.Drawing.Point(91, 118);
this.progressBar1.MarqueeAnimationSpeed = 50;
this.progressBar1.Name = "progressBar1";
this.progressBar1.Size = new System.Drawing.Size(100, 23);
this.progressBar1.Style = System.Windows.Forms.ProgressBarStyle.Marquee;
this.progressBar1.TabIndex = 0;
and use continuous style with marqueeanimationsspeed 0 to stop it
For the Progressbar to do something (apart from the Marquee Style) you need to set the Value property. If you have .Minimum=0 and .Maximum=100 then a .Value of 50 means that the Progressbar is half full. If you should use Continuous or Blocks Style depends on the Visual Styles settings and doesn't make a real difference here on Win 7 (maybe it does for example under Win XP).
The Marquee style means that you don't know how far your task has proceeded. The Progressbar then shows a continously moving piece (is only visible at runtime!!). I just tested it in Win 7 and it works.
Here's a little boilerplate I use with a BackgroundWorker and a ProgressBar and Label.
Public Class BackgroundWorkerUI
Private args As New ProgressArgs
Private Sub bw_DoWork(sender As System.Object, e As System.ComponentModel.DoWorkEventArgs) Handles bw.DoWork
'set ProgressBar style to Marquee
args.Style = ProgressBarStyle.Marquee
bw.ReportProgress(0) 'triggers the progress changed event to update UI on correct thread
'some long operation
Threading.Thread.Sleep(5000)
'Set ProgressBar style to Continuous
args.Style = ProgressBarStyle.Continuous
For i As Integer = 0 To 100
If bw.CancellationPending Then
e.Cancel = True
Exit For
End If
args.Current = i
args.Max = 100
args.Status = String.Format("({0} of {1}) Updating...", args.Current, args.Max)
bw.ReportProgress(0)
'some operation
Threading.Thread.Sleep(100)
Next
End Sub
Private Sub bw_ProgressChanged(sender As Object, e As System.ComponentModel.ProgressChangedEventArgs) Handles bw.ProgressChanged
lblStatus.Text = args.Status
If args.Style = ProgressBarStyle.Marquee Then
bar.Style = args.Style
bar.MarqueeAnimationSpeed = 15
Else
bar.Style = ProgressBarStyle.Continuous
bar.Minimum = args.Min
bar.Maximum = args.Max
bar.Value = args.Current
End If
End Sub
Private Sub bw_RunWorkerCompleted(sender As Object, e As System.ComponentModel.RunWorkerCompletedEventArgs) Handles bw.RunWorkerCompleted
If e.Error IsNot Nothing Then
MessageBox.Show(e.Error.Message, "Background Worker Exception", MessageBoxButtons.OK, MessageBoxIcon.Error)
Else
If e.Cancelled Then
lblStatus.Text = "Operation canceled"
Else
lblStatus.Text = "Done"
End If
End If
End Sub
Private Sub BackgroundWorkerUI_FormClosing(sender As Object, e As FormClosingEventArgs) Handles Me.FormClosing
If bw.IsBusy Then
Dim r = MessageBox.Show("A background process is still running. Are you sure you want to quit?", "Confirm", MessageBoxButtons.YesNo, MessageBoxIcon.Question)
If r = Windows.Forms.DialogResult.Yes Then
bw.CancelAsync()
End If
e.Cancel = True
End If
End Sub
Private Class ProgressArgs
Inherits EventArgs
Public Property Status As String
Public Property Current As Integer
Public Property Min As Integer
Public Property Max As Integer
Public Property Style As ProgressBarStyle
Public Sub New()
Status = ""
Current = 0
Min = 0
Max = 0
Style = ProgressBarStyle.Continuous
End Sub
End Class
End Class
I am having an issue where I get multiple entries in my ListView for the same item if I run my action more than once.
I am creating a simple network scanner/hostname grabber that will add the items to the listview as they come back alive to my ping test.
When I run it the first time it runs fine and creates one entry as it should.
When I run it subsequent times it creates the item as many times as I have ran the code ex. 3rd time hitting start it creates each entry 3 times when it should just create the entry once.
Here is my go button code:
Private Sub Go_Click(sender As Object, e As EventArgs) Handles Go.Click
Dim verifyIP
ListView1.Items.Clear()
chkDone = 0
verifyIP = ipChk(ipAdd.Text)
If verifyIP = 1 Then
ipAddy = Split(ipAdd.Text, ".")
pingTest1.WorkerReportsProgress = True
pingTest1.WorkerSupportsCancellation = False
AddHandler pingTest1.ProgressChanged, AddressOf pingTest1_ProgressChanged
pingTest1.RunWorkerAsync()
pingTest2.WorkerReportsProgress = True
pingTest2.WorkerSupportsCancellation = False
AddHandler pingTest2.ProgressChanged, AddressOf pingTest2_ProgressChanged
pingTest2.RunWorkerAsync()
pingTest3.WorkerReportsProgress = True
pingTest3.WorkerSupportsCancellation = False
AddHandler pingTest3.ProgressChanged, AddressOf pingTest3_ProgressChanged
pingTest3.RunWorkerAsync()
pingTest4.WorkerReportsProgress = True
pingTest4.WorkerSupportsCancellation = False
AddHandler pingTest4.ProgressChanged, AddressOf pingTest4_ProgressChanged
pingTest4.RunWorkerAsync()
While chkDone < 4
wait(25)
End While
Else
MsgBox("IP Invalid")
End If
MsgBox("Done")
End Sub
Here is the code from one of the background workers I am using:
Private WithEvents pingTest1 As BackgroundWorker = New BackgroundWorker
Private Sub pingTest1_DoWork(ByVal sender As Object, ByVal e As DoWorkEventArgs) Handles pingTest1.DoWork
Try
Dim hostCheck
pingResult1 = 0
pingTestDone1 = 0
tryIP1 = ipAddy(0) & "." & ipAddy(1) & "." & ipAddy(2) & ".1"
If My.Computer.Network.Ping(tryIP1) = True Then
'Dim pingsender As New Net.NetworkInformation.Ping
'If pingsender.Send(tryIP).Status = Net.NetworkInformation.IPStatus.Success Then
Try
'Dim host As System.Net.IPHostEntry
hostCheck = ""
'host = System.Net.Dns.GetHostByAddress(tryIP3)
'MsgBox(host.HostName)
'host3 = host.HostName
'hostCheck = System.Net.Dns.GetHostEntry(tryIP3).HostName
hostCheck = System.Net.Dns.GetHostByAddress(tryIP1)
'get the hostname property
hostCheck = hostCheck.HostName
pingTest1.ReportProgress("1", hostCheck)
Catch f As Exception
'MsgBox("Error: " & f.Message)
pingTest1.ReportProgress("1", "No Hostname Found")
End Try
Else
pingResult1 = 2
End If
Catch d As Exception
MsgBox("There was an error trying to ping the IP Address: " & d.Message)
End Try
End Sub
Private Sub pingTest1_ProgressChanged(e.ByVal sender As Object, ByVal e As ProgressChangedEventArgs)
MsgBox("Hey")
Dim str(2) As String
Dim itm As ListViewItem
str(0) = tryIP1 & " Is Alive!!!"
str(1) = e.UserState
itm = New ListViewItem(str)
ListView1.Items.Add(itm)
str(0) = ""
str(1) = ""
itm = Nothing
End Sub
Private Sub pingTest1_RunWorkerCompleted(ByVal sender As Object, ByVal e As RunWorkerCompletedEventArgs) Handles pingTest1.RunWorkerCompleted
chkDone = chkDone + 1
End Sub
I added the Hey box and sure enough the ProgressChanged event gets triggered the amount of times I have hit the Go button. Is it something I have coded incorrectly?
It's most likely because you're adding, but not removing your handlers for the progress changed, so you're handling the event multiple times.
Try adding your Progress Changed Event Handlers when you're instantiating your Background workers, rather than every time you click your button. This way they will only handled once.