Click here to Skip to main content
15,888,454 members
Articles / Programming Languages / Visual Basic
Tip/Trick

Updating a UI Element from Multiple Threads

Rate me:
Please Sign up or sign in to vote.
3.00/5 (3 votes)
18 Feb 2017CPOL3 min read 7.9K   6   1
How to update a UI element from multiple threads

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.

VB.NET
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.

VB.NET
Private Sub DoWork(yourparamater As Integer)
    'partial long running code
    progress += 1
    'rest of long running code
End Sub

The code for updating a ToolStripProgressBar that might need to be run from another thread is below:

VB.NET
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:

VB.NET
Task.WaitAll(myTasks)

So you could have a Form.Shown event handler execute as follows:

VB.NET
Private Sub Form1_Shown(sender As Object, e As EventArgs) Handles Me.Shown
    StartTasks()
    Task.WaitAll(myTasks)
    'more Subs
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:

VB.NET
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)   
    'more Subs    
End Sub

The code...

VB.NET
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

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer private indiviual
United Kingdom United Kingdom
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
QuestionBad approach Pin
Rene Balvert20-Feb-17 2:18
Rene Balvert20-Feb-17 2:18 
Hi,

Your do-loop is updating the progress bar hundreds(maybe thousands) time per second pumping messages into the message loop and probably boosting your CPU to the max.

In a real world app you can start different threads but probably you don't have any info about the progress, unless each thread post some progress to the main thread. A busy animation would to better Smile | :)

Another common mistake if you have a worker thread posting progress on every action:

Worker thread
.while not eof
..readline
..postprogress (fileposition / filesize)


To make your application 'safe' to run on a produtional machine you could let each thread post a 'ready' notification.

Additional you can make another thread that does a WaitAll() and informs the GUI that all threads are ready.

But using a do-loop until one or more threads are ready is a sin.

Good luck.

modified 20-Feb-17 10:06am.

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.