Phius
Phius

Reputation: 724

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()

Upvotes: 6

Views: 2633

Answers (3)

jfs
jfs

Reputation: 414695

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).

Upvotes: 5

liberforce
liberforce

Reputation: 11454

Here's a threaded example. But a non-threaded, non polling approach would be better, because in GTK 3, the threads_enter/threads_leave have been deprecated, so your program would be harder to port to GTK 3 + PyGObject.

In C, one would probably use g_spawn_async_with_pipes. An equivalent in python would be glib.spawn_async I guess, and you'd use glib.io_add_watch to be notified when there's data to read in the standard output.

Upvotes: 0

Cesar
Cesar

Reputation: 717

You should use gtk.threads_enter() when you are inside a thread to every call to gtk and close with gtk.threads_leave() after call him. Something like:

def do_something(self):
        while True:
            time.sleep(0.5)
            gtk.threads_enter()
            #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")
            gtk.threads_leave()
            print "Hello, boy."

and sometimes you need to use:

gtk.gdk.threads_init()
gtk.gdk.threads_enter()
#code section
gtk.gdk.threads_leave()

Upvotes: 0

Related Questions