使用线程将stdout重定向到Tkinter文本小部件的问题

时间:2013-11-30 17:24:19

标签: python multithreading python-2.7 tkinter stdout

我打印无限数字的程序只有在重定向sys.stdout之后打印一些内容时才会按预期工作,否则程序会冻结,为什么会这样?这是一个错误吗?

代码:

import Tkinter as Tk
import sys
import threading

def func():
    i = 0
    while True:
        i +=1
        print i

class Std_redirector(object):
    def __init__(self,widget):
        self.widget = widget

    def write(self,string):
        self.widget.see(Tk.END)
        self.widget.insert("end",string)


root = Tk.Tk()
text = Tk.Text(root)
text.pack()

sys.stdout = Std_redirector(text) #Redirect stdout to Tkinter text widget

#print 'hey' #If you uncomment this line, the program works!

thread1 = threading.Thread(target=func)
thread1.start() #Starts printing

root.mainloop()

如果您取消注释此行,则此脚本有效:print 'hey'

顺便说一下,我的操作系统是Windows 7

1 个答案:

答案 0 :(得分:3)

我修改了这个想法,找到了两种方法让它发挥作用。

1)即使它不是线程安全的,你的方法也能正常工作。唯一的问题似乎是应用程序需要在打印到窗口小部件之前初始化。如果你想“立即”启动第二个线程,而不是从一些回调开始,这对我有用:

root.after(100, thread1.start)

2)第二个更干净的方法基于@falsetru链接的示例。但是它要求你以合理的速度打印到标准输出,这样更新就不会阻塞它。

from Tkinter import *
import threading
import Queue # This is thread safe
import time

class Std_redirector():
    def __init__(self, widget):
        self.widget = widget

    def write(self,string):
        self.widget.write(string)

class ThreadSafeText(Text):
    def __init__(self, master, **options):
        Text.__init__(self, master, **options)
        self.queue = Queue.Queue()
        self.update_me()

    def write(self, line):
        self.queue.put(line)

    def update_me(self):
        while not self.queue.empty():
            line = self.queue.get_nowait()
            self.insert(END, line)
            self.see(END)
            self.update_idletasks()
        self.after(10, self.update_me)

def func():
    i = 0
    while True:
        i += 1
        print i
        time.sleep(0.01)

root = Tk()
text = ThreadSafeText(root)
text.pack()
sys.stdout = Std_redirector(text)

thread1 = threading.Thread(target=func)
thread1.start()

root.mainloop()

根据我从其他GUI工具包的经验,我想使用root.after_idle(),但它没有像我预期的那样工作。