Tkinter GUI 中的多线程,不同类中的线程

Posted

技术标签:

【中文标题】Tkinter GUI 中的多线程,不同类中的线程【英文标题】:Multi threading in Tkinter GUI, threads in different classes 【发布时间】:2016-08-26 11:13:20 【问题描述】:

我目前正在学习 Tkinter GUI 编程。而且我被困在多线程概念的某个地方。尽管这个话题在这里讨论了好几次,我还是无法理解这个概念并将其应用到我的小示例程序中。

下面是我的代码:

from PIL import Image, ImageTk 
from Tkinter import Tk, Label, BOTH
from ttk import Frame, Style
from Tkinter import *
import time


class Widgets(Frame):

    def __init__(self, parent):
        Frame.__init__(self, parent)
        self.grid()
        self.parent = parent
        self.initUI(parent)

    def initUI(self, parent):
        self.parent.title("Count Numbers")

        for r in range(10):
            self.parent.rowconfigure(r, weight=1)    
        for c in range(10):
            self.parent.columnconfigure(c, weight=1)        

        self.button1 = Button(parent, text = "count")
        self.button1.grid(row = 1, column = 1, rowspan = 1, columnspan = 2, sticky = W+E+N+S )
        self.button1["command"] = self.countNum

        self.button2 = Button(parent, text = "say Hello")
        self.button2.grid(row = 1, column = 7, rowspan = 1, columnspan = 2, sticky = W+E+N+S) 
        self.button2["command"] = PrintHello(self).helloPrint

    def countNum(self):
        for i in range(10):
            print i
            time.sleep(2)   

class PrintHello(Frame):

    def __init__(self, parent):
        Frame.__init__(self, parent)
        self.grid()
        self.parent = parent

    def helloPrint(self):
        print "Hello"

def main():
    root = Tk()
    root.geometry("300x200")
    app = Widgets(root)
    root.mainloop()

if __name__ == '__main__':
    main() 

输出是一个带有 2 个按钮的 GUI——第一个打印数字,第二个打印“Hello”。但是在单击第一个按钮时,GUI 会在打印数字时冻结。在寻找解决方案时,我发现“多线程”可能会有所帮助。但是经过几次尝试,我无法将多线程应用到我给定的示例程序中。

【问题讨论】:

【参考方案1】:

Pythonista 的回答非常好。但我想谈一些额外的观点。

GUI 是事件驱动的。它们在处理事件的循环中运行,不时调用您的代码片段(称为回调)。因此,您的代码或多或少是事件循环中的客人。正如您所注意到的,您的代码片段应该很快完成,否则它们会停止事件处理,从而导致 GUI 无响应。这是与教程中经常看到的线性程序完全不同的编程模型。要执行更长时间的计算或任务,您可以将它们分成小块并使用after。或者您可以使用multiprocessing 在另一个进程中向他们发送消息。但是你仍然需要定期检查(再次使用after)他们是否完成了。

以下几点源于正确的多线程处理困难

CPython(最常用的 Python 实现)具有所谓的全局解释器锁。这确保一次只有一个线程可以执行 Python 字节码。当其他线程忙于执行 Python 字节码时,运行 GUI 的线程什么也不做。 所以多线程并不是解决 GUI 无响应问题的特定解决方案。

很多 GUI 工具包都不是线程安全的,tkinter 也不例外。这意味着您应该从运行 mainloop 的线程进行 tkinter 调用。(在 Python 3.x 中,tkinter 已成为线程安全的。)

【讨论】:

【参考方案2】:

这么简单的事情你不需要线程。

GUI 卡住了,因为您将 time.sleep 放入函数中,这会阻塞主线程直到它完成。

只需使用 Tk 内置的after 方法。将您的功能更改为。

def countNum(self, num=0):
    if num < 10:
        print num
        root.after(2000, lambda: self.countNum(num + 1))
    else:
        print "Stopping after call"

after 方法采用以下参数:

after(delay_ms, callback, arguments)

时间以毫秒为单位,1000 毫秒 = 1 秒。因此,我们通过 2,000 毫秒的 2 秒延迟。

【讨论】:

以上是关于Tkinter GUI 中的多线程,不同类中的线程的主要内容,如果未能解决你的问题,请参考以下文章

多线程 Python Tkinter 串行监视器中的按钮问题

Tkinter GUI 冻结 - 解除阻塞/线程的提示?

Qt学习笔记8.Qt中的多线程

如何解决 Tkinter 中的多处理问题?

如果 GIL 存在,Python 中的多线程有啥意义?

Python 3 - 主线程未检测到后台线程中的KeyboardInterrupt,直到用户将鼠标悬停在GUI窗口上