How to update UI during intense computation?

15 views
Skip to first unread message

John Perry

unread,
Dec 18, 2025, 1:18:09 PM (yesterday) Dec 18
to Kivy users support
Hello

I've added a progress bar to my application, so as to show how far along a computationally intense computation has progressed, but... it doesn't update. In fact, the entire UI stalls. Kind of disappointing, since the whole point of a progress bar is to show the progress of an intense computation.

I've encountered this before: UI frameworks that keep the UI on the main thread tend to experience this issue. And Python has the whole single-threaded aspect anyway.

Is there a smart, idiomatic approach to this problem? I looked into the multiprocessing module, so as to perform the intense computation on a separate thread, but that's an incredibly complicated piece of work: you can't return a value from a function, but have to use queues pr pipes.

Some details, if needed:

I've verified that the UI element's `value` field is set by a `print(self.ids["thought_bar"].value)` immediately after the update.

I've also verified that the progress bar updates when I do something like the example in the showcase; i.e., pulse the timer.

I've also verified that if I start the progress bar with value 0, then it updates to full once the computation is finished some moments later.

It just doesn't update between empty and full, probably because the intense computation hogs the CPU.

john perry

elli...@cox.net

unread,
Dec 18, 2025, 2:08:55 PM (yesterday) Dec 18
to kivy-...@googlegroups.com
John,

Here are two options:
  1. Threading: Put your long running task is a separate thread.  While it is true that python is single threaded, the threads will be time shared under one process.   This will allow the UI to continue responding, while executing the long running compute task.  There is a decorator in the Clock module, https://kivy.org/doc/stable/api-kivy.clock.html#module-kivy.clock that can be used to ensure the update happens on the main thread.

  2. Break your long running task up into a number of steps, and use Clock.schedule_once, to advance between steps.  This way your long running tasks does part of its work, schedules the next phase of work, and returns to the kivy event loop.  In the kivy event loop the UI will updated, and then the next phase of your long running code will continue.

Let me know if this helps.  I can share a quick demo of either approach if desired.



From: kivy-...@googlegroups.com <kivy-...@googlegroups.com> on behalf of John Perry <cantani...@gmail.com>
Sent: Thursday, December 18, 2025 11:18 AM
To: Kivy users support <kivy-...@googlegroups.com>
Subject: [kivy-users] How to update UI during intense computation?
 
--
You received this message because you are subscribed to the Google Groups "Kivy users support" group.
To unsubscribe from this group and stop receiving emails from it, send an email to kivy-users+...@googlegroups.com.
To view this discussion visit https://groups.google.com/d/msgid/kivy-users/e20036ce-82fa-4dab-8153-41ba7e9fefbdn%40googlegroups.com.

John Perry

unread,
Dec 18, 2025, 3:01:01 PM (yesterday) Dec 18
to Kivy users support
I might need an example. Looking at what you wrote and at the threading module (I've not used that in almost a decade, since I discovered that it wasn't multitasking) it looks as if I'd have to spawn / start two threads; is that correct?
  • One would perform the computation and invoke the callback.
  • The other would update the UI.
The reason I say this is that I don't see how it would help simply to start a separate thread for the computationally intensive task, only to have to `.join` it in the end -- since `join` would be blocking the thread where it's called.

ElliotG

unread,
Dec 18, 2025, 3:30:21 PM (yesterday) Dec 18
to Kivy users support
You don't need to call join(), you can just let the background thread run to completion.  

Here is an example:

from kivy.app import App
from kivy.lang import Builder
from kivy.uix.boxlayout import BoxLayout
from kivy.clock import Clock

import threading
import time

kv = """
RootBoxLayout:
    orientation: 'vertical'
    ProgressBar:
        id: progress_bar
        max:100
    Button:
        size_hint_y: None
        height: '48dp'
        text: 'push me - the UI is still active' # a dummy to show the UI is still alive
    Button:
        id: start_button
        size_hint_y: None
        height: '48dp'
        text: 'Start Long Task'
        on_release: root.start_task()
"""

class RootBoxLayout(BoxLayout):

    def start_task(self, *_):
        self.ids.start_button.disabled = True
        self.ids.progress_bar.value = 0

        thread = threading.Thread(target=self.long_running_task, daemon=True)
        thread.start()

    def long_running_task(self):
        for i in range(101):
            time.sleep(0.05)  # simulate work

            # Schedule UI update on main thread
            Clock.schedule_once(lambda dt, v=i: self.update_progress(v))

        Clock.schedule_once(lambda dt: self.task_complete())

    def update_progress(self, value):
        self.ids.progress_bar.value = value

    def task_complete(self):
        self.ids.start_button.disabled = False


class ThreadedProgressApp(App):
    def build(self):
        return Builder.load_string(kv)


if __name__ == "__main__":
    ThreadedProgressApp().run()


John Perry

unread,
Dec 18, 2025, 6:10:29 PM (22 hours ago) Dec 18
to Kivy users support
Thank you! If not for the example, I'd likely have lost another 1/2 hour or more trying to figure out why it was complaining, "Cannot change graphics instruction outside the main Kivy thread". But it makes complete sense that we need a lambda there.
Reply all
Reply to author
Forward
0 new messages