At long last, Xojo has introduced what might be the most-requested feature of all time: preemptive threads. But what are they, when and how do you use them, and what are their limitations? Let’s get into it…
In The Beginning
Threads have been a part of of the Xojo language for a long time, and by now experienced users know the basics: subclass a Thread
, fill in the Run
event, then call Start
. The code in Run
will execute independently from the rest of your code leaving your app responsive while it does work in the background.
Mostly.
See, until now, a Thread
was only “cooperative”, meaning it ran alongside your main code with the framework deciding when to switch between them and how long each should execute. While the main code was running, the Thread
code was paused, and while the Thread
was running, the main code was paused.
Think of it like standing before two devices that perform different functions when cranked, but only having one crank available. You run the first machine for a bit, then the other, then back to the first, until they are done. And you do it really, really fast.
This worked pretty well when your goal was, say, to keep your user interface responsive. You could spin long-running code into a Thread
, then present the results once it finished.
But it was inadequate if your objective was to speed up some intense process, or the code in your Thread
did not present logical places to yield time back to your main code. In the former scenario, you were better off not using a Thread
, or perhaps using Worker
(a severely limited earlier attempt at allowing simultaneous processing), while in the latter you would have to manually yield back processing time, leading to even slower operation.
In The Now
Cooperative threads are fine for many tasks, but when you need something that truly runs independently within your app, a “preemptive” thread is required.
A preemptive thread spins off onto another core in your machine, which probably has at least four, and runs there entirely isolated from other threads. It’s like launching a separate, mini-app that you can share information with.
The limitations that apply to cooperative threads drop away. Since it runs on a different core, it cannot slow down or interfere with your main code, and will run as fast as its core will allow.
Challenging Times
This sounds almost too good to be true, but it’s not without its challenges. While cooperative threads were rather easy to work with, preemptive threads require an extra layer of thought and awareness for Xojo users.
When a cooperative thread’s code is running, you know nothing else is, so you don’t have to worry about, say, updating a Dictionary
or removing an element from an array. But with preemptive threads, code runs simultaneously with other code so you cannot just update that Dictionary
if there is a chance that the main code, or another preemptive thread, might be doing it too.
Consider this example.
// In our thread
If MyArray.Count = 0 And FinishUp Then
Exit
Else
Process MyArray.Pop
End If
//////////////////////////////
// In the main code
FinishUp = True
MyArray.Add 1
You might think the thread is going to process the last element and then exit its loop because FinishUp
was set to True
, but with preemptive threads we have to be aware of race conditions where the order of actions is unpredictable. Here, the main code could set FinishUp
to True
, and the thread could see that MyArray
was empty and exit before the main code could add the last element. Conditions can change at literally any instant, even in the middle of a single line of code.
Fortunately, there are ways to protect against this and you must use them to ensure orderly operation and avoid surprises.
Know Your Type
The first step is creating a preemptive thread, and Xojo’s implementation is pretty straightforward. Rather than introducing a unique class, you can continue to work with the existing Thread
by setting its new Type
property to Thread.Types.Preemptive
. The Type
can be set when instantiating the Thread
or from within its running code. You can even switch types at will, although once running, the Type
can only be changed from within the Thread
.
Example:
// A subclass of Thread called PThread
MyThread = New PThread
PThread.Type = Thread.Types.Preemptive
PThread.Start
Or within the Run
event, you could do this:
Me.Type = Thread.Types.Preemptive
You can also set Type
through the Inspector after dragging a Thread
to a window.
Protect Yourself
Now that you have a preemptive thread ready to go, you have to think about how to protect common resources. Xojo has done a good job of making a lot of the framework safe, but that just means your app shouldn’t crash if, say, the Thread
tries to manipulate a variable at the same time as other code. It’s up to you to make sure one area of your app doesn’t clobber the changes made by another.
Consider this code that maintains a Dictionary
of Integer arrays.
// Thread code
Var arr() As Integer
If MyDictionary.HasKey(x) Then
arr = MyDictionary.Value(x)
Else
MyDictionary.Value(x) = arr
End If
arr.Add 1
//////////////////////////////
// Main code
If Not MyDictionary.HasKey(x) Then
Var arr() As Integer
MyDictionary.Value(x) = arr
End If
Run this long enough and you’re going to wonder why some data got lost. Why? Because both the Thread
and main code might be checking for the key at the exact same instant and filling it in when it’s not found. If the Thread
does it first, the main code will almost instantly replace it with an empty array.
Instead, you should use a Semaphore
or CriticalSection
to make sure code is protected.
You’d first instantiate one of these in a common property like a Window
or Module
. (I’m using Semaphore
here, but it works the same for CriticalSection
.)
MySemaphore = New Semaphore
MySemaphore.Type = Thread.Types.Preemptive
Notice the new Type
property? It must match the one in Thread
and can be used between the main code and a Thread
or between same-typed threads. (This is one of the limitations of Xojo’s implementation: you cannot share a Semaphore
or CriticalSection
between preemptive and cooperative threads.)
The example can be updated like this:
// Thread code
Var arr() As Integer
MySemaphore.Signal
If MyDictionary.HasKey(x) Then
arr = MyDictionary.Value(x)
Else
MyDictionary.Value(x) = arr
End If
MySemaphore.Release
arr.Add 1
//////////////////////////////
// Main code
MySemaphore.Signal
If Not MyDictionary.HasKey(x) Then
Var arr() As Integer
MyDictionary.Value(x) = arr
End If
MySemaphore.Release
This will force either the main code or Thread
to wait for the other to complete before continuing, ensuring proper order and avoiding the race condition.
Limitations
Using preemptive threads comes with certain restrictions. The biggest one is the aforementioned inability to share a Semaphore
or CriticalSection
with a cooperative thread. The other is the same as in a cooperative thread: you cannot update a user interface component directly instead relying on the UserInterfaceUpdate
mechanism (more on that below).
If you are setting up multiple threads to work on different parts of some object, that may crash hard depending on the type of object. For example, writing to the same file or different parts of a MemoryBlock
is fine, but updating a Picture
may not be. In those cases, you will have to get creative.
What’s It Good For?
Choosing preemptive threads over cooperative means that you either have some code that needs to truly run in the background that would otherwise interfere with you user interface, or you have some large task that can be logically processed in parts.
Window Service
In the former case, the easiest technique might be to drag a Thread
to your window, set its Type
, and implement the Run
event. It can be started from within the window either through a manual process like a pressing a button, or automatically when the window opens.
Just like with a cooperative thread, you can send data back to your window using the AddUserInterfaceUpdate
method and implementing the UserInterfaceUpdate
event.
Splitting It Up
If your task involves something that can be split up into logical chunks, I encourage you to look at my ThreadPool
module, accessible through the Xojo Examples or my GitHub page. (The latter has a README with more details.)
The ideal usage for ThreadPool
is when you have some data that can be processed in sections. For example, suppose you wanted to count the vowels in a large file. You could subclass ThreadPool
and implement its Process
event to count one block. You would then send the file into your subclass in chunks of, say, 256k each and gather the results.
The benefit of ThreadPool
, among other things, is that it will handle the coordination for you so the dangers of using preemptive threads are minimized. You still have to consider how to send your results back to your main code safely, perhaps using Semaphore
/CriticalSection
or the AddUserInterfaceUpdate
method, but each block can be considered independently of the others.
(A big thanks to MVP Anthony Cyphers for writing the example included with Xojo.)
Juggling Chainsaws
The power of preemptive threads is clear, but the potential pitfalls can be serious. Unless you’re very careful, you can easily create bugs that may not be readily apparent, and may take hours or even days to track down. (I say this from unfortunate experience.) Unless you have a compelling reason to take on that challenge, don’t bother.
What I’m trying to say is, if a cooperate thread does the job adequately, there is no need to introduce the headache that preemptive threads can bring.
But if you do have a legitimate need, like a long-running process that needs to run quickly without blocking your UI, go for it, just be careful.
In any case, it’s great that we finally have that option.
Kem Tekinay is a Mac consultant and programmer who has been using Xojo since its first release to create custom solutions for clients. He is the author of the popular utilities TFTP Client and RegExRX (both written with Xojo) and lives in Connecticut with his wife Lisa, and their cat.
Need More Information? Read more about preemptive threads in the Xojo Documentation and Blog. Or read about Semaphore and CriticalSection, also on the Xojo Blog.