UserGuide

Threading

From Xojo Documentation

Threads provide a way for you to run code separately from the main thread that is also used by the user interface. They are particularly useful for long-running tasks that might otherwise make your application appear as if it is “frozen” because no user interface updates can occur.

Threads are cooperative, which means two things:

  • They are relatively easy to use
  • They only run on a single CPU core

Pre-emptive threading, which has the ability to run your code on multiple CPU cores is much more complex and not something that is supported by Xojo. If you need to run processes on multiple cores, you should consider using separate helper console applications and communicate with them. One technique is shown in the UserGuide:Multiprocessing topic.

Creating a Thread

To create a thread, first you need to add a Thread object to your project (iOS projects use Xojo.Threading.Thread). You can do this by dragging a Thread from the Library onto a window, web page or to the Navigator.

The code that you want to run in the thread is placed in the Run event handler. Anything you call from the thread is considered part of the thread and also runs in the background.

To start a thread you call its Run method, which calls the code in the Run event handler.

How CPU Time is Shared

Threads can yield time to other threads each time they execute a looping construct such as For, While, and Do. However, a thread does not necessarily yield time at every loop boundary even though it has an opportunity to do so. A thread actually yields to another thread when the Thread Scheduler decides that its timeslice has expired. Context switches are expensive, so the Thread Scheduler always tries to avoid them.

You can also force context switches by calling Application.YieldToNextThread or by calling Application.SleepCurrentThread.

Thread Priority

By default a thread has a priority of 5. This is the same priority as the main application thread, so if you leave your thread at 5 it will have the same amount of time allocated to it as the main thread.

For example, presume there are 100 "units" of thread time available. If both the main thread and your thread have a priority of 5 then the time unit split is calculated like this:

Total Priority = 5 (main) + 5 (your thread) = 10
Time Units (your thread) = (5/10) * 100 = 50
Time Units (main thread) = (5/10) * 100 = 50

This means that the main thread runs 50 times and your thread runs 50 times. But what if you want your thread to run more often because it is doing some heavy processing? In this case you would increase its priority. If you change your thread’s priority to 15 then the time unit split is calculated the same, but results in more time units for your thread:

Total Priority = 5 (main) + 15 (your thread) = 20
Time Units (your thread) = (15/20) * 100 = 75
Time Units (main thread) = (5/20) * 100 = 25

This means your thread will get 75 of the 100 time units and the main thread will get only 25. So your thread is running 3 times more often than the main thread.

Thread Control

Threads can be slept, suspended, resumed and killed. When you sleep a thread, you specify the amount of time (in milliseconds) for the thread to sleep. It will automatically wake itself when the time has elapsed. If you suspend a thread, it stays suspended until you specifically resume it. Finally you can kill a thread, which terminates it.

Each of these actions changes the state of the thread. You can check the thread state any time using the State property. A thread can be Running (0), Waiting (1), Suspended (2), Sleeping (3) or NotRunning (4).

Sharing Resources

Sometimes you may have a resource (data or a file, for example) that needs to be used by multiple threads that are all currently running. An example would be that a thread tries to open a file for writing that another thread has already opened for writing. This can cause issues and unwanted exceptions.

You can manage this by using a CriticalSection, Semaphore or Mutex to prevent multiple threads from trying to access the same shared resource.

Communicating with the User Interface

Because of operating system restrictions, threads can not directly access or manipulate any user interface element. Should a thread access a UI element your app will raise a ThreadAccessingUIException.

If you have a thread that needs to update user interface in some way, such as updating a Progress Bar, you should instead use a Timer as an intermediary. Rather than having your thread update a Progress Bar directly, a Timer periodically get the progress value from the thread and then the Timer (which runs on the main application UI thread) updates the Progress Bar.

Here is an example of the Run event handler of a thread that was added to a window. The thread loops through an array (with a pause in the middle). The position in the array is stored as a property of the window (ArrayPosition) as is the maximum value of the array (ArraySize):

Dim arrayValues() As Integer
arrayValues = Array(1, 2, 3, 4, 5, 6, 7, 9, 10)
ArraySize = arrayValues.Ubound
For i As Integer = 0 To ArraySize
ArrayPosition = i
App.SleepCurrentThread(1000) // Pause for 1 second
Next

A separate timer can check the value for ArrayPosition and ArraySize and use them to update the Progress Bar. This code is in the Action event handler of a Timer on the window:

If CountThread.State = Thread.NotRunning Then
Me.Enabled = False
MsgBox("Finished!")
Else
ThreadProgress.Value = ArrayPosition
ThreadProgress.Maximum = ArraySize
End If

In the Action event handler of a button on the window, this code starts the thread and the timer:

If CountThread.State = Thread.NotRunning Then
CountThread.Run
CountTimer.Period = 500
CountTimer.Mode = Timer.ModeMultiple
CountTimer.Enabled = True
End If

As the Thread runs, it updates the ArrayPosition property on the window. The Timer periodically updates (about every 1/2 a second) the Progress Bar on the window with the value of the property.

This two-step process prevents the thread from directly updating the user interface.

Using the Task class

Included with Xojo is an example of a Task class that can be used to do this as well (Desktop/UpdatingUIFromThread/UIThreadingWithTask). Task is a Thread subclass that has an UpdateUI event handler and method. Use it in place of a Thread when you want your thread to be able to update the user interface. In the Task’s Run event handler, you call the UpdateUI method when you want to update any UI. Then in the UpdateUI event handler, you can have code that directly accesses the UI.

When you call UpdateUI, you can pass either a list of values (using a series of Pairs) or you can pass a Dictionary of values. Regardless, in the UpdateUI event handler, you get a dictionary of the values. You can then check the values in the dictionary to determine what to update in the UI.

Example Projects

  • Examples/Advanced/Thread/Semaphore Example
  • Examples/Desktop/Threading/ThreadingExample
  • Examples/Desktop/UpdatingUIFromThread/UIThreadingWithTimer
  • Examples/Desktop/UpdatingUIFromThread/UIThreadingWithTask
  • Examples/Graphics and Multimedia/DrawingWithThreads
  • Examples/iOS/Framework/ThreadExample
  • Examples/Web/Controls/LargeData
  • Examples/Web/Controls/ThreadProgress

See Also

Thread, CriticalSection, Semaphore, Mutex, ThreadAccessingUIException classes; UserGuide:Framework, UserGuide:Timers, UserGuide:Multiprocessing topics