我有一棵树,只需单击按钮即可用计算机上的文件列表填充树。当程序填充树时,整个 GUI 挂起。
有没有办法在另一个线程中填充树,以便在找到文件时填充树,而不是在所有内容都已添加后才填充?或者还有其他想法吗?
它看起来是这样的:
我用以下代码填充树:
def traversePaths(self, path, parent):
files = os.listdir(path)
files.sort()
for myfile in files:
if myfile[0] != ".":
if os.path.isdir(path + os.sep + myfile):
current = self.filelist.append(parent,[self.dirIcon,self.dirIcon,self.dirOpenIcon,myfile,"DD",True,True,3])
self.traversePaths(path + os.sep + myfile, current)
else:
current = self.filelist.append(parent,[self.fileIcon,self.dirIcon,self.dirOpenIcon,myfile,"AA",True,True,3])
单击按钮时执行:
def on_refreshbutton_clicked(self, button):
self.traversePaths(self.path.get_filename(), None)
我不认为threading.Thread 可以工作,因为 gui 的 gtk 线程无法找到有关该主题的 gi.repository.Gtk api
有任何想法吗?
答案1
当询问编程问题时,最好提供一个最小的工作示例,而不仅仅是代码片段。
由于没有可用的示例,我有以下建议:
- 使用线程遍历目录树并将结果放入第二个单独的树模型(未连接到树视图)。这意味着您有两个树模型,一个已连接(将呈现到屏幕上),另一个未连接(因此不会呈现)
- 添加几秒钟的超时,它会调用一个函数来分离并删除第一个树模型,将第二个树模型复制到一个新的树模型,该树模型现在用作“线程”树模型并附加第二个树模型。
您看到 GUI 挂起的原因是文件遍历器耗时很长,并且每次添加文件时,都会调用 Gtk 中的大量开销。通过添加完整的树模型,此开销仅被调用一次。并且通过使用线程进行文件遍历,您的 GUI 保持响应。
至于 Gtk 中的线程部分,您可能需要看看这里:https://stackoverflow.com/questions/8120860/python-doing-some-work-on-background-with-gtk-gui
您的代码有几点注释:
- Python 有一个内置的文件遍历器,它可能更快但肯定比您的代码更短:
os.walk
- 如果您想改用代码,请记住 Python 具有内置递归限制。根据文件系统的大小,您可能需要用类似 trampoline 的构造替换递归
答案2
这是一个使用单个 TreeStore 同时更新树的完整示例:
#!/usr/bin/python
import os
import threading
import time
from itertools import cycle
from gi.repository import GObject, Gtk
GObject.threads_init() # all Gtk is in the main thread;
# only GObject.idle_add() is in the background thread
HEARTBEAT = 20 # Hz
CHUNKSIZE = 100 # how many items to process in a single idle_add() callback
def chunks(seq, chunksize):
"""Yield N items at a time from seq."""
for i in xrange(0, len(seq), chunksize):
yield seq[i:i + chunksize]
class TreeStore(Gtk.TreeStore):
__gtype_name__ = 'TreeStore'
def __init__(self, topdir, done_callback=None):
Gtk.TreeStore.__init__(self, str) # super() doesn't work here
self.path2treeiter = {topdir: None} # path -> treeiter
self.topdir = topdir
self.done_callback = done_callback
self._cv = threading.Condition()
t = threading.Thread(target=self._build_tree)
t.daemon = True
t.start() # start background thread
def _build_tree(self, _sentinel=object()):
# executed in a background thread
cv = self._cv
p = self.path2treeiter
for dirpath, dirs, files in os.walk(self.topdir):
# wait until dirpath is appended to the tree
cv.acquire()
while p.get(dirpath, _sentinel) is _sentinel:
cv.wait()
parent = p[dirpath]
cv.release()
# populate tree store
dirs[:] = sorted(d for d in dirs
if d[0] != '.') # skip hidden dirs
for chunk in chunks(dirs, CHUNKSIZE):
GObject.idle_add(self._appenddir, chunk, parent, dirpath)
for chunk in chunks(sorted(files), CHUNKSIZE):
GObject.idle_add(self._appendfile, chunk, parent)
GObject.idle_add(self.done_callback)
def _appenddir(self, chunk, parent, dirpath):
# executed in the main thread
self._cv.acquire()
p = self.path2treeiter
for d in chunk:
p[os.path.join(dirpath, d)] = self.append(parent, [d])
self._cv.notify()
self._cv.release()
def _appendfile(self, chunk, parent):
# executed in the main thread
for f in chunk:
self.append(parent, [f])
class Window(Gtk.Window):
__gtype_name__ = 'Window'
def __init__(self, topdir):
super(Window, self).__init__(type=Gtk.WindowType.TOPLEVEL)
self.__start_time = time.time()
self.__title = 'GTK Tree MultiThreading Demo'
self.set_title(self.__title)
self.set_default_size(640, 480)
# create tree
tree_store = TreeStore(topdir, self._on_tree_completed)
tree_view = Gtk.TreeView()
tree_view.set_model(tree_store)
cell = Gtk.CellRendererText()
tree_view.append_column(Gtk.TreeViewColumn(topdir, cell, text=0))
scrolled_window = Gtk.ScrolledWindow()
scrolled_window.add(tree_view)
self.add(scrolled_window)
# update title to show that we are alive
self._update_id = GObject.timeout_add(int(1e3 / HEARTBEAT),
self._update_title)
def _on_tree_completed(self):
if self._update_id is None:
return
# stop updates
GObject.source_remove(self._update_id)
self._update_id = None
self.set_title('%s %s %.1f' % (self.__title, ' (done)',
time.time() - self.__start_time))
def _update_title(self, _suff=cycle('/|\-')):
self.set_title('%s %s %.1f' % (self.__title, next(_suff),
time.time() - self.__start_time))
return True # continue updates
win = Window(topdir=os.path.expanduser('~'))
win.connect('delete-event', Gtk.main_quit)
win.show_all()
Gtk.main()
作为@xubuntix 说在这种情况下,每种方法都treestore.append()
很贵。看看你是否可以接受。