Skip to content

Optimizing Xojo Code, Part 5: Threads and UserInterfaceUpdate

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 HasKey before 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 StatusLabel and any other visual controls.

How It Works

  1. Button press: Click the button and the thread starts.
  2. Background loop: The thread iterates from 0 to kCountTo, sleeping 10 ms each step. After each step, it queues an update.
  3. 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: