Reputation: 31
Trying to understand background workers :)
Imports System.Threading
Imports System
Imports System.IO
Imports System.Windows.Forms
Imports System.ComponentModel
Imports System.Text.RegularExpressions
Imports System.Text
Imports System.Diagnostics
Imports System.Drawing
Public Class CLViewForm
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
BackgroundWorker1.RunWorkerAsync()
End Sub
Function SystemLoads()
Dim PCV As Integer = Convert.ToDecimal(PerformanceCounter1.NextValue())
Me.Label5.Text = PCV & " %"
Me.ProgressBar1.Minimum = 0
Me.ProgressBar1.Maximum = 100
Me.ProgressBar1.Step = 1
Me.ProgressBar1.Value = PCV
Me.Label6.Text = Now.ToLongTimeString
Return 0
End Function
Private Sub BackgroundWorker1_DoWork(sender As Object, e As DoWorkEventArgs) Handles BackgroundWorker1.DoWork
Me.Invoke(SystemLoads())
End Sub
End Class
The basic gist of what I am trying to do is just load a background worker that updates 3 things on the primary form a progressbar that shows current system load a text label that shows the load by percent and a clock
I know I can do all this with a simple timer, but when I go to add my other timers it tends to make the form all kinds of sluggish so I wish to learn how to shoot most of these things I want to do into a background thread.
I am new to all this so I do not know how to call these things and be thread safe about it and as such I get an error [of course, which is why I am here asking :)] that says "vb.net cross-thread operation not valid control 'progressbar1' accessed from a thread other than which is it called"
or something to that effect.
Using the code sample I supplied how can I make the background thread update the form's progress bar and % label?
TIA
Upvotes: 0
Views: 15973
Reputation: 10931
You have been provided better solutions, but here are the changes I use to get your idea working:
BackgroundWorker
only executes the DoWork
event once. So call RunWorkerAsync
again in RunWorkerCompleted
event.AddressOf
, but Control.Invoke
is too general, so you have to specify one. However, the framework provides MethodInvoker
. Thus, change SystemLoads
to a Sub
.PerfomaceCounters
don't seem to adhere to 0<=%<=100
.So the simple changes to get you past the current roadblocks give you:
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
BackgroundWorker1.RunWorkerAsync()
End Sub
Sub SystemLoads()
Dim PCV As Integer = Convert.ToInt32(PerformanceCounter1.NextValue())
Me.Label5.Text = PCV & " %"
Me.ProgressBar1.Minimum = 0
Me.ProgressBar1.Maximum = 100
Me.ProgressBar1.Step = 1
Me.ProgressBar1.Value = Math.Min(Math.Max(0, PCV), 100)
Me.Label6.Text = Now.ToLongTimeString
End Sub
Private Sub BackgroundWorker1_DoWork(sender As Object, e As DoWorkEventArgs) Handles BackgroundWorker1.DoWork
Me.Invoke(New Methodinvoker(AddressOf SystemLoads))
End Sub
Private Sub BackgroundWorker1_RunWorkerCompleted(sender As Object, e As RunWorkerCompletedEventArgs) Handles BackgroundWorker1.RunWorkerCompleted
BackgroundWorker1.RunWorkerAsync()
End Sub
This leads to a few other issues that I've addressed in this LinqPad query, specifically:
ObjectDisposedException
on closing the window. I tried mitigating it, but, in the end, I just ignored it when the Form
has been disposed.Sleep
a bit to reduce the updates, to reduce sluggishness when resizing the form. This is where it shows a Timer
would be better.If InvokeRequired
pattern even if it's not needed here.NB I had to fabricate my own control locations and they're barely OK as it stands.
Also, you'll have to change the PerformanceCounter
to at least an InstanceName
that exists on your system.
Upvotes: 0
Reputation: 31
The trick I use is dreadful but it works:
pgbMain.Maximum += 1
pgbMain.Value += 2
pgbMain.Value -= 1
pgbMain.Maximum -= 1
Or you could use:
i = pgbMain.Value + 1
pgbMain.Value = pgbMain.Maximum
pgbMain.Value = i
But the latter method means that the final step is as delayed as usual as there is no 'backward' step. (No, neither method is visually detectable.)
You can see on the WinForm when adjusting the progress bar Value property manually that it gradually moves to higher values but instantly moves to lower ones. So this seems to get rid of the lag. Sadly.
Upvotes: 0
Reputation: 4765
Progress bar is kinda goofy and confusing. I had to resort to keeping all my work in the same form (if you can). So in your case I might try doing your systemloads inside a procedure in the same form. That's what worked for me. I hope it helps. I have pasted some code that might explain it a bit (can't get it to format right in SO- sorry) but in a nutshell:
Declaration
Public Event DoWork As DoWorkEventHandler
Public Event PrgRprt As ProgressChangedEventHandler
Public g_intProgress As Integer
progress bar info
Private Sub btnStepOne_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnStepTwo.Click
'Calls BackgroundWorker_DoWork, which in turn calls step One
Me.BackgroundWorker1.RunWorkerAsync()
End Sub
Private Sub BackgroundWorker1_DoWork(ByVal sender As Object, ByVal e As System.ComponentModel.DoWorkEventArgs) Handles BackgroundWorker2.DoWork
'Called from Me.BackgroundWorker1.RunWorkerAsync()
'calls step One proc, where the work is done
Call StepOne(strInput)
End Sub
Private Function StepOne(strInput as string)as string
'this is where I do my work that the progress bar will monitor
Dim x as integer
For x = 0 to 100
'Do your calculations as you loop
'This is where you report back to the worker to change progress bar
Me.BackgroundWorker1.ReportProgress(x)
Next x
End function
Private Sub BackgroundWorker1_ProgressChanged(ByVal sender As Object, ByVal e As System.ComponentModel.ProgressChangedEventArgs) Handles BackgroundWorker2.ProgressChanged
Me.pbConvertion.Value = e.ProgressPercentage
Me.g_intProgress = e.ProgressPercentage
Me.pbConvertion.Maximum = "100"
Me.pbConvertion.Update()
Me.lblProgbar.Text = "Percentage - " & Me.pbConvertion.Value & "% Completed"
End Sub
Private Sub BackgroundWorker1_RunWorkerCompleted(ByVal sender As Object, ByVal e As System.ComponentModel.RunWorkerCompletedEventArgs) Handles BackgroundWorker1.RunWorkerCompleted
Me.lblProgbar.Text = "Step One Completed"
End Sub
Upvotes: 0
Reputation: 1546
You need to set:
Backgroundworker can use progressbar
Backgroundworker can report progress
Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
BW1.RunAsync()
End Sub
Then:
Private Sub BW1_DoWork(ByVal sender As System.Object, ByVal e As System.ComponentModel.DoWorkEventArgs) Handles BW1.DoWork
Dim i as integer = 1
Do Until integer = 0
IF BW1.CancellationPending = True then
MsgBox("Cancelled by user")
Exit Sub
End If
....
BW1.ReportProgress(i)
Loop
End Sub
Show the progress:
Private Sub BW1_ProgressChanged(ByVal sender As Object, ByVal e As System.ComponentModel.ProgressChangedEventArgs) Handles BW1.ProgressChanged
Progressbar1.Value = e.ProgressPercentage
End Sub
EDIT:
You can't use UI Controls directly on DoWork method. You need to delegate them first but for showing progress (progressbar, label, etc) Backgroundworker has it owns method (ProgressChanged()) that let you use the UI in a safe mode.
Upvotes: 0
Reputation: 27322
I have tried to create a simple example here. You are on the right lines by using a delegate but I think your inplementation is a little off.
A form that has a label
, a progressbar
and a backgroundworker
control on it is what you need then drop the following code in:
Private Sub TestForm_Load(sender As System.Object, e As System.EventArgs) Handles MyBase.Load
'Start the worker
BackgroundWorker1.RunWorkerAsync()
End Sub
Private Sub BackgroundWorker1_DoWork(sender As Object, e As System.ComponentModel.DoWorkEventArgs) Handles BackgroundWorker1.DoWork
'simulate long running processes
UpdateStatus(0, "Loading")
System.Threading.Thread.Sleep(1000)
UpdateStatus(33, "One third of the way through")
System.Threading.Thread.Sleep(1000)
UpdateStatus(66, "Two thirds of the way through")
System.Threading.Thread.Sleep(1000)
UpdateStatus(100, "Finished")
End Sub
'all calls to update the progress bar and label go through here
Private Sub UpdateStatus(ByVal progress As Integer, ByVal status As String)
Try
If Me.InvokeRequired Then
Dim cb As New UpdateStatusCallback(AddressOf UpdateStatusDelegate)
Me.Invoke(cb, New Object() {progress, status})
Else
UpdateStatusDelegate(progress, status)
End If
Catch ex As Exception
MessageBox.Show("There was an error " + ex.Message, "Error", MessageBoxButtons.OK, MessageBoxIcon.Error)
End Try
End Sub
Private Delegate Sub UpdateStatusCallback(ByVal progress As Integer, ByVal status As String)
'This catually updates the control - modify the paramters and update to suit the control you are using
Private Sub UpdateStatusDelegate(ByVal progress As Integer, ByVal status As String)
ProgressBar1.Value = progress
Label1.Text = status
End Sub
Upvotes: 1
Reputation: 1153
Create an event for the background worker RunWorkerCompleted and ProgressChanged..
C#
this.backgroundWorker.ProgressChanged += new System.ComponentModel.ProgressChangedEventHandler(/* the event method */);
this.backgroundWorker.RunWorkerCompleted += new System.ComponentModel.RunWorkerCompletedEventHandler(/* the event method */);
Also, do not forget to set the WorkerReportsProgress to true..
Upvotes: 0