quornian
quornian

Reputation: 10153

Managing an event loop in a thread

I'm writing a class in a library that has an event processing loop:

class EventSystem(object):

    def __init__(self):
        self.queue = Queue.Queue()
        self.thread = threading.Thread(target=self_process_loop)
        self.thread.daemon = True
        self.thread.start()

    def _process_loop(self):
        while True:
            event = self.queue.get()
            self._handle_event(event)

    def _handle_event(self, event):
        ...

I've set the thread to be a daemon thread so it exits with the main program, but this means it could be killed mid-processing. I really want to wait for the current processing iteration to complete before it is killed.

Normally in these situations there's a flag being checked in the while loop and some method - stop(), say - that sets it False. I'd rather avoid this requirement if at all possible.

Is the following considered bad?

    def _process_loop(self):
        while True:
            event = self.queue.get()
            self.thread.daemon = False
            self._handle_event(event)
            self.thread.daemon = True

Edit: Yes it's bad: RuntimeError: cannot set daemon status of active thread

What would be the proper way to achieve this?

Upvotes: 0

Views: 2777

Answers (3)

quornian
quornian

Reputation: 10153

Going on from umläute's answer (the idea of the child receiving info from the parent thread), I realised I can monitor the parent thread's state.

Making the thread non-daemon removes the issue of it exiting mid-processing. Then checking the parent thread to see if it's still running I can exit cleanly:

class EventSystem(object):

    def __init__(self):
        self.queue = Queue.Queue()
    self.parent_thread = threading.current_thread()          # <-- Added
    self.thread = threading.Thread(target=self_process_loop)
    self.thread.start()

def _process_loop(self):
    while self.parent_thread.is_alive():                    # <-- Changed
        try:
            event = self.queue.get(timeout=1)               # <-- Timeout added
        except Queue.Empty:
            continue
        else:
            self._handle_event(event)

def _handle_event(self, event):
    ...

This introduces polling but is a viable solution that doesn't require the client to join with, or stop, the background thread.

Upvotes: 0

uml&#228;ute
uml&#228;ute

Reputation: 31274

  • use a condition variable that controls the life-cycle

  • catch the signal that "kills" your daemon

  • whenever the signal occurs, set the terminating condition, so the daemon quits once it has processed all events

Upvotes: 1

justhalf
justhalf

Reputation: 9107

Your idea is correct that you should (see the note in this documentation) use a flag, however you cannot use the internal daemon property as the flag, as it will return an error.

Documentation in Python:

daemon
A boolean value indicating whether this thread is a daemon thread (True) or not (False). This must be set before start() is called, otherwise RuntimeError is raised. Its initial value is inherited from the creating thread; the main thread is not a daemon thread and therefore all threads created in the main thread default to daemon = False.

So you should create another variable to function as the flag.

Upvotes: 1

Related Questions