pygtk:在打印大输出数据集时使用io_add_watch块实现的异步输出

时间:2013-10-10 11:46:28

标签: python asynchronous stream gtk pygtk

我正在用python命令行模拟器编写GTK + GUI程序。我的python命令行实现为gtk.TextView,可用于输出prints的结果(以及从TextView和exec读取命令,但我不在此处发布输入部分因为它与问题无关。)

我使用以下技术在真实终端和我的python命令行之间发送stdout流:

r_out, w_out = os.pipe() # create a pipe, cause sys.stdout is unreadable, thus we can't poll it for available output
w_out_stream = os.fdopen(w_out, "w", 0) # open write-end of pipe with 0-length buffer
sys.stdout = w_out_stream # now prints and sys.stdout.writes would go to the pipe
watch = gobject.io_add_watch(r_out, gobject.IO_IN, stdout_callback) # poll the read-end of pipe for data and call stdout_callback

def stdout_callback(stream, condition):
    data = os.read(stream, 1) # read the pipe byte-by-byte
    iter = textbuf.get_end_iter() # textbuf is buffer of TextView, the GUI widget, I use as python command line
    textbuf.insert(iter, data) # output stdout to GUI
    sys.__stdout__.write(data) # output stdout to "real" terminal stdout

gtk.main()

这对小输出效果很好。但不幸的是,当输出变得相对较大(如数千字节)时,我的应用程序挂起并且不显示任何输出。

但是,如果我发送SIGINT,我的输出会出现在GUI和真实终端中。显然,我希望它没有SIGINT。任何想法,是什么导致这样的阻碍?

1 个答案:

答案 0 :(得分:4)

外部进程在使用os.read时阻止了UI,你应该使用glib.spawn_async生成进程并使用IOChannel来读取输入类似于:

from gi.repository import Gtk, GLib

class MySpawned(Gtk.Window):
    def __init__(self):
        Gtk.Window.__init__(self)

        vb = Gtk.VBox(False, 5)
        self.tw = Gtk.TextView()
        bt = Gtk.Button('Run')
        bt.connect('clicked', self.process)

        vb.pack_start(self.tw, True, True, 0)
        vb.pack_start(bt, False, False, 0)

        self.add(vb)
        self.set_size_request(200, 300)
        self.connect('delete-event', Gtk.main_quit)
        self.show_all()

    def run(self):
        Gtk.main()

    def process(self, widget, data=None):
        params = ['python', '-h']

        def write_to_textview(io, condition):
            print condition
            if condition is GLib.IO_IN:
               line = io.readline()
               self.tw.props.buffer.insert_at_cursor(line)
               return True
            elif condition is GLib.IO_HUP|GLib.IO_IN:
                GLib.source_remove(self.source_id)
                return False

        self.source_id = None

        pid, stdin, stdout, stderr = GLib.spawn_async(params,
                                        flags=GLib.SpawnFlags.SEARCH_PATH,                                       
                                        standard_output=True)

        io = GLib.IOChannel(stdout)

        self.source_id = io.add_watch(GLib.IO_IN|GLib.IO_HUP,
                                 write_to_textview,
                                 priority=GLib.PRIORITY_HIGH)
if __name__ == '__main__':
    s = MySpawned()
    s.run()

有很多线程告诉你,你可以使用线程或其他东西,请不要这样做,上面的例子不会阻止你的UI,即使在一个漫长的过程,输出将被打印在textview上,也适用于Windows(但它会打开一个丑陋的控制台窗口,直到GLib中的错误得到修复)。