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.
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.
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.
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).
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.
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):
arrayValues = Array(1, 2, 3, 4, 5, 6, 7, 9, 10)
ArraySize = arrayValues.LastRowIndex
For i As Integer = 0 To ArraySize
ArrayPosition = i
App.SleepCurrentThread(1000) // Pause for 1 second
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:
Me.Enabled = False
ThreadProgress.Value = ArrayPosition
ThreadProgress.Maximum = ArraySize
In the Action event handler of a button on the window, this code starts the thread and the timer:
CountTimer.Period = 500
CountTimer.Mode = Timer.ModeMultiple
CountTimer.Enabled = True
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.
- Examples/Advanced/Thread/Semaphore Example
- Examples/Graphics and Multimedia/DrawingWithThreads