问题
I'm writing a GTK+ GUI program with a python command line emulator. My python command line is implemented as a gtk.TextView, which can be used to output results of prints
(and to read commands from TextView and exec
them, but I don't post the input part here as it has nothing to do with the question).
I use the following technique to tee the stdout
stream between the real terminal and my python command line:
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()
This works pretty nice with small output. But unfortunately, when the output becomes relatively large (like thousands of bytes), my application hangs and doesn't display any output.
However, if I send a SIGINT
, my output appears both in GUI and in real terminal. Obviously, I want it there without SIGINT. Any ideas, what causes such a block?
回答1:
The external process is blocking the UI while using os.read, you should spawn the process using glib.spawn_async and use a IOChannel to read the input something like:
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()
There are like a gazillion threads out there telling you that you could use threads or other stuff, please don't do that, the above example will not block you UI even on a long process and the output will be printed on the textview, works on windows too (but it will open an ugly console window until a bug in GLib will get fixed).
来源:https://stackoverflow.com/questions/19294929/pygtk-asynchronous-output-implemented-with-io-add-watch-blocks-when-printing-la