Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

pygtk and multiprocessing - Constantly update textview from another process running infinite loop

I'm building a python app with pygtk. It consists of some buttons that activate/deactivate some infinite looped processes and a textview that should keep showing whats going on inside each process. Like verbose stuff.

These processeses hasn't an end. They stop only when the user hit it's corresponding button (or close the app).

What's going wrong: I cant print stuff in the textview from these processes. Maybe because they haven't an end...

Actually the app is too big to show the whole code here. So I've made a simple and little example of what I'm doing.

import pygtk
pygtk.require("2.0")
import gtk
import time
import glib
from multiprocessing import Process
gtk.threads_init()

class Test(gtk.Window):
    def delete_event(self, widget, event, data=None):
        if isinstance(self.my_process, Process):
            if self.my_process.is_alive():
                self.my_process.terminate()
        gtk.main_quit()
        return False

    def __init__(self):

        gtk.Window.__init__(self)
        self.set_default_size(500, 400)
        self.set_title(u"Test")
        self.connect("delete_event", self.delete_event)

        self.mainBox = gtk.VBox(False, 5)

        self.text = gtk.TextView()
        self.text.set_wrap_mode(gtk.WRAP_WORD)
        self.button = gtk.Button("Start")

        self.add(self.mainBox)
        self.mainBox.pack_start(self.text, True, True, 0)
        self.mainBox.pack_start(self.button, False, True, 0)

        self.button.connect("clicked", self.start_clicked)

        self.show_all()

    def start_clicked(self, widget):
        self.register_data("Starting...")
        self.my_process = Process(target=self.do_something)
        self.my_process.start()

    def do_something(self):
        while True:
            time.sleep(0.5)
            #get a list of a lot of things
            #Do stuff with each item in the list
            #show me on the gui whats going on
            glib.idle_add(self.register_data, "Yo! Here I'm")
            print "Hello, boy."

    def register_data(self, data):
        data = data + "\r\n"
        #gtk.gdk.threads_enter()
        buff = self.text.get_buffer()
        biter = buff.get_start_iter()
        buff.insert(biter, data)
        #gtk.gdk.threads_leave()


if __name__ == "__main__":
    mnc = Test()
    mnc.set_position(gtk.WIN_POS_CENTER)
    gtk.threads_enter()
    gtk.main()
    gtk.threads_leave()
like image 441
Phius Avatar asked Nov 22 '12 18:11

Phius


1 Answers

Remove all .threads_init(), .threads_enter(), .threads_leave(). multiprocessing is not threading.

Put data you'd like to display into multiprocessing.Queue() in your child process:

def do_something(self):
    while True:
        #get a list of a lot of things
        #Do stuff with each item in the list
        #show me on the gui whats going on
        self.data_queue.put("Yo! Here I'm")

and poll it in GUI loop:

def __init__(self, ...):
    # ...
    self.data_queue = Queue()
    gobject.timeout_add(100, self.update_text) 

where:

def update_text(self):
    # receive updates from the child process here
    try:
        data = self.data_queue.get_nowait()
    except Empty:
        pass # nothing at this time
    else:
        self.register_data(data)
    return True

To avoid polling you could write to multiprocessing.Pipe in your child process and setup GUI callback using gobject.io_add_watch(). Here's a complete code example:

#!/usr/bin/python3
from multiprocessing import Pipe, Process
import gi
gi.require_version('Gtk', '3.0')
from gi.repository import GObject, Gtk

# create GUI to show multiprocessing output
win = Gtk.Window()
win.set_default_size(640, 480)
label = Gtk.Label('process output')
win.add(label)

# start dummy infinite loop in a child process
def loop(conn):
    import itertools, sys, time
    for i in itertools.count():
        conn.send(i)
        time.sleep(0.1 - time.monotonic() % 0.1)

parent_conn, child_conn = Pipe(duplex=False)
Process(target=loop, args=[child_conn], daemon=True).start()
child_conn.close()

# read values from the child
def read_data(source, condition):
    assert parent_conn.poll()
    try:
        i = parent_conn.recv()
    except EOFError:
        return False # stop reading
    # update text
    label.set_text('Result from the child: %03d' % (i,))
    return True # continue reading
# . configure the callback
GObject.io_add_watch(parent_conn.fileno(), GObject.IO_IN, read_data)

win.connect('delete-event', Gtk.main_quit)
win.show_all()
Gtk.main()

You can also do it with an arbitrary subprocess (not just a python child process).

like image 94
jfs Avatar answered Sep 26 '22 03:09

jfs