Running code in a thread
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 can be cooperative or preemptive:
Type |
Description |
---|---|
Cooperative |
The default type. A cooperative thread shares time with your app (the main thread) and any other cooperative threads you have running. It can access the entire Xojo framework. |
Preemptive |
A preemptive thread runs on its own CPU core independently of your app. Only parts of the Xojo framework that are not user interface can be safely accessed. Accessing an unsafe framework object (such as a Window) will cause the app to crash. |
Note
Pre-emptive threading can be complex. If you need to run processes on multiple cores, another consideration is using separate helper console applications and communicate with them. One technique is shown in the Creating Helper Apps topic.
Creating a Thread
To create a thread, first you need to add a Thread object to your project (iOS projects use 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.
If you want the thread to be preemptive, make sure you set the Type property before you call the Run method. Once the thread is running, the Type of the thread can only be changed from within the thread itself.
Tip
If you want to know how many logical cores there are on the device upon which your app is running you can call the System.CoreCount function.
Cooperative thread priority
As the name suggests, the Priority property of the Thread class dictates how much priority that thread should have in terms of time allocated to it to execute. This applies to cooperative threads only.
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).
Overflowing the stack
No stack overflow checking is done on preemptive threads. If your stack gets too deep, your app will crash.
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.
This is even more important for a preemptive thread as it can be interrupted at any time. When used in a preemptive thread, both CriticalSection and Semaphore have a Type property that must be set to preemptive to work properly.
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):
Var arrayValues() As Integer
arrayValues = Array(1, 2, 3, 4, 5, 6, 7, 9, 10)
ArraySize = arrayValues.LastIndex
For i As Integer = 0 To ArraySize
ArrayPosition = i
Thread.SleepCurrent(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.ThreadState = Thread.ThreadStates.NotRunning Then
Me.Enabled = False
MessageBox("Finished!")
Else
ThreadProgress.Value = ArrayPosition
ThreadProgress.Maximum = ArraySize
End If
In the Pressed event handler of a button on the window, this code starts the thread and the timer:
If CountThread.ThreadState = Thread.ThreadStates.NotRunning Then
CountThread.Run
CountTimer.Period = 500
CountTimer.RunMode = Timer.RunModes.Multiple
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 Pair 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.
See also
Thread, CriticalSection, Semaphore, Mutex, ThreadAccessingUIException classes; Running code periodically with a Timer topic