Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Block main thread until python background thread finishes side-task

I have a threaded python application with a long-running mainloop in the background thread. This background mainloop is actually a call to pyglet.app.run(), which drives a GUI window and also can be configured to call other code periodically. I need a do_stuff(duration) function to be called at will from the main thread to trigger an animation in the GUI, wait for the animation to stop, and then return. The actual animation must be done in the background thread because the GUI library can't handle being driven by separate threads.

I believe I need to do something like this:

import threading

class StuffDoer(threading.Thread):
    def __init__(self):
        threading.Thread.__init__(self)
        self.max_n_times = 0
        self.total_n_times = 0
        self.paused_ev = threading.Event()

    def run(self):
        # this part is outside of my control
        while True:
            self._do_stuff()
            # do other stuff

    def _do_stuff(self):
        # this part is under my control
        if self.paused_ev.is_set():
            if self.max_n_times > self.total_n_times:
                self.paused_ev.clear()
        else:
            if self.total_n_times >= self.max_n_times:
                self.paused_ev.set()
        if not self.paused_ev.is_set():
            # do stuff that must execute in the background thread
            self.total_n_times += 1

sd = StuffDoer()
sd.start()

def do_stuff(n_times):
    sd.max_n_times += n_times
    sd.paused_ev.wait_for_clear()   # wait_for_clear() does not exist
    sd.paused_ev.wait()
    assert (sd.total_n_times == sd.max_n_times)

EDIT: use max_n_times instead of stop_time to clarify why Thread.join(duration) won't do the trick.

From the documentation for threading.Event:

wait([timeout])

Block until the internal flag is true. If the internal flag is true on entry, return immediately. Otherwise, block until another thread calls set() to set the flag to true, or until the optional timeout occurs.

I've found I can get the behavior I'm looking for if I have a pair of events, paused_ev and not_paused_ev, and use not_paused_ev.wait(). I could almost just use Thread.join(duration), except it needs to only return precisely when the background thread actually registers that the time is up. Is there some other synchronization object or other strategy I should be using instead?

I'd also be open to arguments that I'm approaching this whole thing the wrong way, provided they're good arguments.

like image 722
Mu Mind Avatar asked Feb 03 '26 14:02

Mu Mind


1 Answers

Hoping I get some revision or additional info from my comment, but I'm kind of wondering if you're not overworking things by subclassing Thread. You can do things like this:

class MyWorker(object):
  def __init__(self):
    t = Thread(target = self._do_work, name "Worker Owned Thread")

    t.daemon = True

    t.start()

  def _do_work(self):
    While True:
      # Something going on here, forever if necessary.  This thread
      # will go away if the other non-daemon threads terminate, possibly
      # raising an exception depending this function's body.

I find this makes more sense when the method you want to run is something that is more appropriately a member function of some other class than it would be to as the run method on the thread. Additionally, this saves you from having to encapsulate a bunch of business logic inside of a Thread. All IMO, of course.

like image 143
g.d.d.c Avatar answered Feb 06 '26 02:02

g.d.d.c