Introduction
This article shows how to update a ToolStripProgressBar
while multiple threads are running, and to stop updating it when all the threads are complete.
Background
While writing a program to execute a time consuming piece of code multiple times with differing parameters, I needed to update the progress bar while they were running, and to stop updating the progress bar when they had all completed.
It is easy enough to update a progress bar - in this case, a ToolStripProgressBar
- while one thread is running. However, having multiple processes, updating the ProgressBar
and figuring out when all the processes had completed was a problem. This is my attempt at a solution.
Below is a simplified version of the code which should be applicable to other tasks.
Using the Code
So, initially I used an array of Task
to create multiple tasks that ran the same DoWork
code. This was fairly straightforward - I created the array with 8 elements simply because I have 8 cores in my processor. Change this to suit your computer.
You might want to also note the line "Dim clientNumber as Integer = i
". The compiler warns that using a loop index directly with multi-threading can give unexpected results so it's good practice to use a variable declared in the block.
Imports System.Threading.Tasks
Dim progress As Integer
Dim myTasks(7) As Task
Private Sub StartTasks()
For i As Integer = 0 To numberOfThreads - 1
Dim index As Integer = i
myTasks(index) = Task.Factory.StartNew(Sub() DoWork(index))
Next
End Sub
The code that is run multiple times can be whatever you want. In my case, it was to download multiple emails from the same server using multiple client connections, but you can do whatever you need to do. The important thing is that this code changes a global variable which will be used further down.
Private Sub DoWork(yourparamater As Integer)
progress += 1
End Sub
The code for updating a ToolStripProgressBar
that might need to be run from another thread is below:
Private Delegate Sub SetProgressBarDelegate(i As Integer)
Private Sub SetProgressBarValue(p As Integer)
If Me.InvokeRequired Then
Dim d As New SetProgressBarDelegate(AddressOf SetProgressBarValue)
Me.Invoke(d, New Object() {p})
Else
Me.ToolStripProgressBar1.Value = p
End If
End Sub
So far, we have a sub
that starts several threads running the DoWork sub
and a sub
to update the progress bar without lag while other code is running. To tie all this together, let's look at the next bit which for the purpose of this article is in the Form
's .Shown
event handler.
Normally after you start multiple tasks, the rest of your code carries on executing, but you can force the rest of your code to wait until all the tasks in the MyTask
array have completed by using the line:
Task.WaitAll(myTasks)
So you could have a Form.Shown
event handler execute as follows:
Private Sub Form1_Shown(sender As Object, e As EventArgs) Handles Me.Shown
StartTasks()
Task.WaitAll(myTasks)
End Sub
What I needed to do however, is after the tasks have been started, keep updating the progress bar until all the tasks are done. To do this, I used a loop which keeps looping until all the tasks are completed. So the Form.Shown
event handler now looks like this:
Private Sub Form1_Shown(sender As Object, e As EventArgs) Handles Me.Shown
StartTasks()
Do
SetProgressBarValue(progress)
Loop Until Array.TrueForAll(myTasks, Function(x) x.Status = TaskStatus.RanToCompletion)
End Sub
The code...
Loop Until Array.TrueForAll(myTasks, Function(x) x.Status = TaskStatus.RanToCompletion)
...checks the array "MyTasks
" to see if the .Status
property of each element =TaskStatus.RanToCompletion
and returns True
to the Loop Until
if they all are.
So now, you have a sub
that will start multiple tasks, and do other things while the tasks are running and stop doing those things when the tasks are complete. Voila!
History
- 18th February, 2017: First draft