Search code examples
pythonmultiprocessingglib

Python multprocessing callback


Using this post as inspiration, I am trying to add a callback. I am using GLib.add_timeout to poll for the result, as I want to use it in a Gtk app. However, the main_quit() is not called properly, and thus the following code hangs after finishing:

import multiprocessing
import queue
import collections
import gi
gi.require_version("Gtk", "3.0")
from gi.repository import GLib, Gtk

Msg = collections.namedtuple("Msg", ["event", "args"])


class BaseProcess(multiprocessing.Process):
    "A process backed by internal queues for simple messaging"

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.requests = multiprocessing.Queue()
        self.responses = multiprocessing.Queue()

    def send(self, event, *args, finished_callback=None):
        "Puts the event and args as a `Msg` on the requests queue"
        msg = Msg(event, args)
        self.requests.put(msg)
        GLib.timeout_add(100, self._monitor_process, finished_callback)

    def run(self):
        while True:
            event, args = self.requests.get()
            if event == "quit":
                break
            handler = getattr(self, "do_%s" % event, None)
            if not handler:
                raise NotImplementedError("Process has no handler for [%s]" % event)
            msg = handler(*args)
            self.responses.put(msg)

    def _monitor_process(self, finished_callback):
        print(f"in _monitor_process {finished_callback}", flush=True)
        try:
            result = self.responses.get(False)
            if finished_callback is not None:
                finished_callback(result)
        except queue.Empty:
            return GLib.SOURCE_CONTINUE
        return GLib.SOURCE_REMOVE

class MyProcess(BaseProcess):
    "test process class"

    def do_sum(self, arg1, arg2):
        "test method"
        print(f"do_sum {arg1 + arg2}", flush=True)
        return arg1 + arg2


def finished_callback(result):
    print(f"result {result}", flush=True)
    Gtk.main_quit()

if __name__ == "__main__":
    process = MyProcess()
    process.start()
    process.send('sum', 1, 2, finished_callback=finished_callback)
    Gtk.main()

How can I prevent the code from hanging?

Edit: I see from this page that others have noted problems. How can I build a Gtk-based app to control long-running processes like scanners without blocking the main thread?


Solution

  • The threading docs for pyGObject lead me to the answer, which is to add process.daemon = True before starting the process:

    if __name__ == "__main__":
        process = MyProcess()
        process.daemon = True
        process.start()
        process.send('sum', 1, 2, finished_callback=finished_callback)
        Gtk.main()