Skip to content

Preemptive Threads Are Here, and They Are… Pretty Good

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.