Python tkinter与线程同时使用导致崩溃

5

我编写了一个使用线程的Python tkinter代码,以便tkinter向导通过在主线程中运行的tkinter mainloop自动更新,并在单独的线程中运行后台进程。但是我注意到,在运行代码时,Python会在一段时间后崩溃。此外,它的性质是随机的,但大多数情况下Python都会崩溃。我编写了一个小的测试代码,可以展示这个问题(我的原始代码类似于这个,但具有一些真实的进程和许多其他功能,因此我分享了测试代码)。

######################################################################
 # Test Code for Tkinter with threads
import Tkinter
import threading
import Queue
import time

# Data Generator which will generate Data
def GenerateData(q):
    for i in range(1000000):
        #print "Generating Some Data, Iteration %s" %(i)
        time.sleep(0.01)
        q.put("Some Data from iteration %s. Putting this data in the queue for testing" %(i))

# Queue which will be used for storing Data
q = Queue.Queue()

def QueueHandler(widinst, q):
    linecount = 0
    while True:
        print "Running"
        if not q.empty():
            str = q.get()
            linecount = linecount + 1
            widinst.configure(state="normal")
            str = str + "\n"
            widinst.insert("end", str)
            if linecount > 100:
                widinst.delete('1.0', '2.0')
                linecount = linecount - 1
            widinst.see('end')
            widinst.configure(state="disabled")

# Create a thread and run GUI & QueueHadnler in it
tk = Tkinter.Tk()
scrollbar = Tkinter.Scrollbar(tk)
scrollbar.pack(side='right', fill='y' )
text_wid = Tkinter.Text(tk,yscrollcommand=scrollbar.set)
text_wid.pack()
t1 = threading.Thread(target=GenerateData, args=(q,))
t2 = threading.Thread(target=QueueHandler, args=(text_wid,q))
t2.start()
t1.start()

tk.mainloop()
######################################################################

复现步骤:

如果在IDLE中打开此代码并运行它,有时会出现挂起状态。为了复现此问题,请将睡眠时间从0.01修改为0.1并运行它。之后停止应用程序,将其修改回0.01,保存并运行。这一次它会运行,但一段时间后Python会停止工作。我使用的是Windows 7(64位)。

问题:

我已经向Python错误提交了该问题,但被拒绝了。但我从stackoverflow上的一个问题中得到了使用队列写入tkinter的想法。有人能提供建议来处理它吗?

编辑后的代码:

# Test Code for Tkinter with threads
import Tkinter
import threading
import Queue
import time

# Data Generator which will generate Data
def GenerateData(q):
    for i in range(1000000):
        #print "Generating Some Data, Iteration %s" %(i)
        time.sleep(0)
        q.put("Some Data from iteration %s. Putting this data in the queue for testing" %(i))

# Queue which will be used for storing Data
q = Queue.Queue()

def QueueHandler():
    global widinst, q
    linecount = 0
    if not q.empty():
        str = q.get()
        linecount = linecount + 1
        widinst.configure(state="normal")
        str = str + "\n"
        widinst.insert("end", str)
        if linecount > 100:
            widinst.delete('1.0', '2.0')
            linecount = linecount - 1
        widinst.see('end')
        widinst.configure(state="disabled")
        tk.after(1,QueueHandler)

# Create a thread and run GUI & QueueHadnler in it
tk = Tkinter.Tk()
scrollbar = Tkinter.Scrollbar(tk)
scrollbar.pack(side='right', fill='y' )
text_wid = Tkinter.Text(tk,yscrollcommand=scrollbar.set)
text_wid.pack()
t1 = threading.Thread(target=GenerateData, args=(q,))
#t2 = threading.Thread(target=QueueHandler, args=(text_wid,q))
#t2.start()
widinst = text_wid
t1.start()
tk.after(1,QueueHandler)
tk.mainloop()

这个想法要么不同,要么就是错误的。关于线程和GUI的问题并不局限于Tk,我知道的大多数GUI工具包都会施加相同的限制:只能在运行GUI的线程中更新GUI。为了纠正你的例子:将队列读取以及随之而来的GUI更新移动到主线程中。GenerateDate继续在它自己的线程上运行。 - mmgp
更新后的代码有问题,因为现在你的主线程没有留给工作线程运行的机会。我看到你在工作线程中放了一个time.sleep(0),这使它将控制权让给另一个线程(在这种情况下是主线程)。现在,在你的主线程中,你调用了tk.after(1, ...),它在任何时候都不会将控制权交还给其他线程。所以,你只需要在那里也添加一个time.sleep(0)。现在两个线程都很好地运行,允许彼此运行。 - mmgp
3个回答

8

Tkinter不是线程安全的;您不能从除主线程之外的任何地方访问Tkinter小部件。您需要重构代码,以便QueueHandler在主线程中运行。


现在我看到另一个问题:- 在我的代码中,我使用after调用QueueHandler来更新GUI。但是,如果我取消注释GenerateData线程中的打印语句,则只会在控制台上看到打印语句,GUI永远不会更新。一旦我把那个打印语句注释回去,GUI就开始填充了。有一个实例,我可以同时看到打印语句和GUI更新。这似乎是随机的??您能否对此行为发表评论? - sarbjit
请查看您的问题的评论,这与Tk和Tkinter几乎无关(适用于大多数GUI工具包)。 - mmgp

3

链接失效了。 - Carcigenicate

1
Tkinter 被设计为线程安全的,但由于错误,在2.x3.x中并不是完全线程安全的。
在修复发布之前(以及旧版本中),您应该使用mtTkinter(它被设计为可替换的解决方法)。

谢谢。你知道我能否在Python 2.7中使用mtTkinterttk吗?例如,import mttkinter然后import ttk - actual_panda

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接