Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Updating a UI Element from Multiple Threads

0.00/5 (No votes)
18 Feb 2017 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.

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)
    '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:

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)
    '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:

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

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 has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here