In Part 1, we learned why tight loops with DoEvents freeze your UI. Part 2 showed us Timer controls. Part 3 moved timers into code. Part 4 simplified scheduling with CallLater.
But timers have a limit: they still run on the UI thread. If your background work is genuinely heavy (database queries, file I/O, complex calculations), a timer won’t save you. You need actual background processing and that’s where threads come in.
The Core Idea: Threads
A thread is a separate execution path that runs outside the UI thread. While your thread does the heavy lifting, the UI stays responsive to clicks, typing, scrolling. When your thread finishes a chunk of work, it notifies the UI thread via UserInterfaceUpdate, and only that callback runs on the main thread.
The rule is simple: threads cannot touch UI controls directly. If you try, you get a crash. UserInterfaceUpdate is your bridge.
For a deeper dive into how Xojo’s threading model works under the hood, including cooperative vs. preemptive threading, see Cooperative to Preemptive: Weaving New Threads Into Your Apps.
Let’s Code
Prerequisites: Add a StatusLabel (DesktopLabel), a DesktopButton, and a Thread control. Also define:
kCountTo As Integer = 100
The Thread Control
Drag a Thread from the library into your window and name it BackgroundWorker.
The Button (Start the Thread)
Place this in the Pressed event of a button:
Sub Pressed()
If BackgroundWorker.ThreadState = Thread.ThreadStates.NotRunning Then
BackgroundWorker.Start
End If
End Sub
This checks if the thread is already running. If not, start it. This prevents multiple threads from piling up if someone clicks fast.
The Thread.Run Method (Background Work)
Implement the Run method on your Thread:
Sub Run()
For i As Integer = 0 To kCountTo
' Do background work here. No UI access allowed.
' It can be: database query, file read, API call, pretty much anything that might block "freeze" the UI of the app
' Prepare a Dictionary payload for the UI
Var info As New Dictionary
info.Value("progress") = i
info.Value("message") = "working"
Me.AddUserInterfaceUpdate(info)
' Sleep (optional)
Thread.SleepCurrent(10) // Sleep 10 ms
Next
End Sub
The key line is Me.AddUserInterfaceUpdate(info). This queues data for the UI thread to process. Each call adds one item to a queue, and the UI thread drains it in UserInterfaceUpdate.
The Thread.UserInterfaceUpdate Method (UI Sync)
Implement this event to handle updates from the thread:
Sub UserInterfaceUpdate(data() As Dictionary)
' Runs on the main UI thread. Safe to update controls here.
' Guard against empty or malformed data
If data = Nil Or data.Count < 0 Then Return
Var d As Dictionary = data(0)
' Extract the progress value safely
Var progress As Integer = 0
If d.HasKey("progress") Then
progress = d.Value("progress").IntegerValue
End If
' Update the UI
StatusLabel.Text = progress.ToString
' Mark completion when we hit the target
If progress >= kCountTo Then
StatusLabel.Text = "done"
End If
End Sub
Important notes:
data()is an array of Dictionaries.- Always check
HasKeybefore accessing a value to avoid runtime errors. - Use
.IntegerValue(or.StringValue, etc.) to safely extract values. - This runs on the UI thread, so it is safe to update
StatusLabeland any other visual controls.
How It Works
- Button press: Click the button and the thread starts.
- Background loop: The thread iterates from 0 to
kCountTo, sleeping 10 ms each step. After each step, it queues an update. - Responsiveness: While the thread works, you can still interact with the rest of your application. No freezing.
A Note on Thread Safety
Threads are powerful but risky if misused. Here is the boundary:
- Safe in Run(): Anything that doesn’t touch UI controls. File I/O, database queries, calculations, network calls.
- Unsafe in Run(): Reading or writing UI control properties directly.
StatusLabel.Text = ...is a no no! - Safe in UserInterfaceUpdate(): All UI control access. This runs on the main thread.
If you forget this rule and try to update a label from inside Run(), your app will crash with a ThreadAccessingUIException. Instead pass data through AddUserInterfaceUpdate.
When to Use Threads vs. Timers
Use timers for:
- Short, frequent tasks (10-50 ms ticks).
- Simple progress updates.
- Cases where you are just deferring work a bit.
Use threads for:
- Long-running operations (seconds, minutes, hours).
- Genuinely blocking work (I/O, network, heavy calculations).
- Multiple independent background tasks.
In this series, we moved from blocking the UI to using timers on the UI thread, then to actual background threads. Each approach solves a different problem.
Wrapping Up
Threads are the heavy artillery of responsive design. They run independent of the UI and communicate safely via UserInterfaceUpdate. Once you grasp the boundary between thread code and UI code, you can build applications that never freeze, no matter how much work is happening behind the scenes.
See the Thread documentation for more details on priority, stack size, and advanced patterns.
Until then, happy threading!
Gabriel is a digital marketing enthusiast who loves coding with Xojo to create cool software tools for any platform. He is always eager to learn and share new ideas!
Optimizing Code Series:
