Python3 Tkinter Treeview在多线程时性能低下

Python3 Tkinter Treeview在多线程时性能低下,python,python-3.x,multithreading,tkinter,treeview,Python,Python 3.x,Multithreading,Tkinter,Treeview,Tl;dr:1500次从另一个线程插入到Treeview小部件需要40-340秒才能完成,但是如果在主线程上插入,或者在根窗口太小以至于无法看到Treeview时,只需要1.2-1.7秒即可完成 我正在做一个涉及神经网络识别游戏图像的小项目,我试图弄明白为什么我的结果树视图在识别图像后更新得如此缓慢。下面的代码是我遇到的一般问题的MCVE 我的应用程序目前对网络进行训练,根据训练模型进行预测,然后在显示这些预测的结果时,显示1500个结果所需的时间比训练和预测所需的时间都长。在我的应用程序和本例

Tl;dr:1500次从另一个线程插入到Treeview小部件需要40-340秒才能完成,但是如果在主线程上插入,或者在根窗口太小以至于无法看到Treeview时,只需要1.2-1.7秒即可完成

我正在做一个涉及神经网络识别游戏图像的小项目,我试图弄明白为什么我的结果树视图在识别图像后更新得如此缓慢。下面的代码是我遇到的一般问题的MCVE

我的应用程序目前对网络进行训练,根据训练模型进行预测,然后在显示这些预测的结果时,显示1500个结果所需的时间比训练和预测所需的时间都长。在我的应用程序和本例中,我发现填充按钮完成和显示所有结果所需的时间通常与窗口的高度(或Treeview小部件的高度)成正比,本例显示当前机器上所有1500行输出所需的时间介于40到340秒之间。我尝试使用cProfile和pstats来确定是什么更具体地导致了延迟,但是我对它们仍然缺乏经验,尽管我知道大约99%的时间都花在了{method'call'of{u tkinter.tkapp'objects}和{method'globalsetvar'of{u tkinter.tkapp'objects}上,但我不知道这两种方法是什么,或者如何处理这些信息

然而,我发现如果在窗口太小以至于无法显示Treeview时启动worker函数,那么显示所有结果大约需要1.2-1.7秒。这可以通过观察我的示例中的进度条以及窗口越小,进度条的进度就越快来明显看出。由于它可以在这么长的时间内显示所有结果,这表明(至少对我来说)在插入结果时,树视图可见所花费的绝大多数时间都花在反复渲染文本和更新滚动条高度上。为此,我一直试图找到一种方法,一次插入大量行,或者至少在每次更改后不重新渲染树视图,但似乎没有找到任何方法可以做到这一点

在尝试创建此MCVE时,我发现如果我在主线程上调用add_entries函数而不是在另一个线程上调用它,则显示所有结果所需的时间同样短(~1.5秒)。虽然我认为这可能是一个可行的解决方案,但我很好奇,在试图获得更多关于一个问题的信息的同时,是否有更好的解决方案,我一直在努力寻找

到目前为止,我发现的最接近的一个是关于如何使用类似的模块(GTK3)有人遇到类似的问题,解决方案是将Treeview设置为固定高度的模型,但是我找不到任何关于普通tkinter Treeview小部件可以使用的类似选项的信息,而不仅仅是GTK3,我很好奇tkinter中是否存在这样的选项,或者这是其他模块独有的,或者是否有更好的解决方案,我还没有完全想到

导入随机、线程
导入cProfile,pstats
从tkinter进口*
从tkinter.font导入字体
从tkinter.ttk导入进度栏,Treeview
从字符串导入可打印
COLS=列表(范围(10))
行数=1500
def____;main___;()
root=Tk()
程序\窗口=应用程序(根)
尝试:
root.destroy()
除错误外:
通过
类应用程序(框架):
def uuu init uuu(self,parent=None):
self.parent=parent
self.bar_int=IntVar(值=0)
self.tree=Treeview(self.parent,columns=COLS,show=“headers”)
self.vsb=滚动条(self.parent,orient='vertical',command=self.tree.yview)
self.bar=Progressbar(self.parent,变量=self.bar\u int)
self.btn=按钮(self.parent,text=“Populate”,command=self.add\u条目)
对于col中的col:
self.tree.heading(col,text=str(col))
self.tree.column(col,width=Font().measure(str(col)))
self.tree.grid(行=0,列=0,sticky='nsew',列span=2)
self.vsb。网格(行=0,列=2,粘性=nsew')
自助吧。网格(行=1,列=0,粘性=nsew')
self.btn。网格(行=1,列=1,粘性=nsew',列span=2)
self.parent.columnconfigure(0,权重=1)
self.parent.rowconfigure(0,权重=1)
self.parent.geometry('300x200')
self.parent.mainloop()
def添加_条目(自身):
worker=threading.Thread(目标=self.add\u条目\u worker)
worker.start()
def添加条目\工作人员(自我):
self.tree.delete(*self.tree.get_children())
self.bar.configure(最大值=行)
将cProfile.Profile()作为配置文件:
对于范围内的i(行):
自整定(一)
li=[random.sample(可打印,10)表示COLS中的i]
self.tree.insert(“”,'end',value=li)
ps=pstats.Stats(配置文件)
ps.打印统计数据()
如果名称=“\uuuuu main\uuuuuuuu”:
__主要的
更新:在hussic的建议下,在与一位朋友讨论了这个问题后,我研究了root.after()和Queue。我能够让我目前的代码在一定程度上符合hussic的建议,但它似乎仍然不稳定,我不确定我是否做了我应该做的一切。下面是我添加的一个新的refresher()函数,以及对add_entries()函数的修改。add_entries_worker()的所有引用都指向tkinter小部件
import random, threading, queue
from tkinter import Tk, TclError, Frame, IntVar, Scrollbar, Button
from tkinter.font import Font
from tkinter.ttk import Progressbar, Treeview
from string import printable

COLS = list(range(10))
ROWS = 1500


def __main__():
    root = Tk()
    program_window = App(root)
    try:
        root.destroy()
    except TclError:
        pass


class Tkworker:
    Empty = queue.Empty

    def __init__(self, root, producer, consumer, ms=200):
        self.root = root
        self.consumer = consumer
        self.ms = ms
        self.queue = queue.Queue()  # type: queue.Queue
        self.thread = threading.Thread(target=producer)

    def start(self):
        self.stop = False
        self.thread.start()
        self._consumer_call()

    def put(self, item):
        self.queue.put(item)

    def get(self):
        return self.queue.get(False)

    def _consumer_call(self):
        self.consumer()
        if not self.stop:
            self.root.after(self.ms, self._consumer_call)


class App(Frame):

    def __init__(self, parent=None):
        self.parent = parent

        self.bar_int = IntVar(value=0)
        self.tree = Treeview(self.parent, columns=COLS, show="headings")
        self.vsb = Scrollbar(self.parent, orient='vertical', command=self.tree.yview)
        self.bar = Progressbar(self.parent, variable=self.bar_int)
        self.btn = Button(self.parent, text="Populate", command=self.start_entries)

        for col in COLS:
            self.tree.heading(col, text=str(col))
            self.tree.column(col, width=Font().measure(str(col)))

        self.tree.grid(row=0, column=0, sticky='nsew', columnspan=2)
        self.vsb. grid(row=0, column=2, sticky='nsew')
        self.bar. grid(row=1, column=0, sticky='nsew')
        self.btn. grid(row=1, column=1, sticky='nsew', columnspan=2)

        self.parent.columnconfigure(0, weight=1)
        self.parent.rowconfigure(0, weight=1)
        self.parent.geometry('300x200')

        self.parent.mainloop()

    def start_entries(self):
        self.chunksize = 500
        self.tkwr = Tkworker(self.parent, self.worker, self.add_entries, ms=100)
        self.tree.delete(*self.tree.get_children())
        self.bar.configure(maximum=ROWS)
        self.num = 0
        self.tkwr.start()

    def worker(self):
        chunk = []
        for i in range(ROWS):
            chunk.append([random.sample(printable, 10) for _ in COLS])
            if  i % self.chunksize == 0:
                self.tkwr.put(chunk)
                chunk = []
        if chunk:
            self.tkwr.put(chunk)
        print('worker end')

    def add_entries(self):
        try:
            chunk = self.tkwr.get()
            for li in chunk:
                self.tree.insert('', 'end', values=li)
            self.num += len(chunk)
            self.bar_int.set(self.num)
        except self.tkwr.Empty:
            if self.num == ROWS:
                self.tkwr.stop = True
                print('stop')


if __name__ == "__main__":
    __main__()