Reputation: 15204
I am trying to create a GUI for my python app and I am struggling to get the Text Widget to behave like a stdout. More specifically, I want to the widget to display the information as soon as it is requested to do so and not at the end when everything is finished.
To demonstrate, consider this:
import sys
import time
import multiprocessing as mp
import multiprocessing.queues as mpq
from threading import Thread
from tkinter import *
class StdoutQueue(mpq.Queue):
def __init__(self, *args, **kwargs):
ctx = mp.get_context()
super(StdoutQueue, self).__init__(*args, **kwargs, ctx=ctx)
def write(self,msg):
self.put(msg)
def flush(self):
sys.__stdout__.flush()
def text_catcher(text_widget,queue):
while True:
text_widget.insert(END, queue.get())
def test_child(q):
sys.stdout = q
print('child running')
def test_parent(q):
sys.stdout = q
print('parent running')
time.sleep(5.)
mp.Process(target=test_child,args=(q,)).start()
if __name__ == '__main__':
gui_root = Tk()
gui_txt = Text(gui_root)
gui_txt.pack()
q = StdoutQueue()
gui_btn = Button(gui_root, text='Test', command=lambda: test_parent(q),)
gui_btn.pack()
monitor = Thread(target=text_catcher, args=(gui_txt, q))
monitor.daemon = True
monitor.start()
gui_root.mainloop()
Hitting the Test
button does not print 'parent running'
right away and 'child running'
5 seconds later as desired, but rather, it waits 5 seconds and prints the two strings at once.
Is there any way to get the desired behaviour?
Upvotes: 1
Views: 810
Reputation: 2516
I do not understand completely what is going on. text_catcher
is being blocked when test_parent
is called from the Tk thread. I would have assumed the monitor thread prevents this. Well...
One possible workaround could be to put test_parent
in its own thread. This can be easily done with a small decorator:
def run_in_thread(fun):
def wrapper(*args, **kwargs):
thread = Thread(target=fun, args=args, kwargs=kwargs)
thread.start()
return thread
return wrapper
@run_in_thread
def test_parent(q):
sys.stdout = q
print('parent running')
time.sleep(5.)
mp.Process(target=test_child, args=(q,)).start()
With test_parent
wrapped like this, the text message appears immediatley.
Upvotes: 1
Reputation: 708
Just move time.sleep(5.)
from test_parent into test_child:
def test_child(q):
sys.stdout = q
time.sleep(5.)
print('child running')
def test_parent(q):
sys.stdout = q
print('parent running')
mp.Process(target=test_child, args=(q,)).start()
Even when I try it without StdoutQueue
it works fine, here is the code:
import time
import multiprocessing as mp
from tkinter import *
def test_child():
time.sleep(5.)
print('child running')
def test_parent():
print('parent running')
mp.Process(target=test_child, args=()).start()
if __name__ == '__main__':
gui_root = Tk()
gui_txt = Text(gui_root)
gui_txt.pack()
gui_btn = Button(gui_root, text='Test', command=lambda: test_parent(), )
gui_btn.pack()
gui_root.mainloop()
Upvotes: 0