Python 如何使用Tkinter GUI正确实现多线程?
我试图在tkinter中使用GUI时实现多线程。我来了,但我没能实现 所以基本上我需要知道: 我需要如何修改我的代码以使Progressbar友好、流畅地交互,即当GUI失去焦点时不进入无响应模式? 以下是我的代码:Python 如何使用Tkinter GUI正确实现多线程?,python,python-3.x,multithreading,user-interface,tkinter,Python,Python 3.x,Multithreading,User Interface,Tkinter,我试图在tkinter中使用GUI时实现多线程。我来了,但我没能实现 所以基本上我需要知道: 我需要如何修改我的代码以使Progressbar友好、流畅地交互,即当GUI失去焦点时不进入无响应模式? 以下是我的代码: from tkinter import * import queue import threading from tkinter.ttk import * class ThreadedTask(threading.Thread): def __init__(self, q
from tkinter import *
import queue
import threading
from tkinter.ttk import *
class ThreadedTask(threading.Thread):
def __init__(self, queue):
threading.Thread.__init__(self)
self.queue = queue
# Gui class
class MyGui(Frame):
def __init__(self, parent):
super().__init__(parent)
self.parent = parent
self.queue = queue.Queue()
self.init_ui()
# define a button to fulfill long task
def init_ui(self):
self.frame = Frame(self, relief=RAISED, borderwidth=1)
self.frame.grid(row=0, column=0, columnspan=1, sticky='ns')
self.status_frame = Frame(self, relief=RAISED, borderwidth=1, height=20)
self.status_frame.grid(row=1, column=0, columnspan=3, sticky='nesw')
self.status_frame.grid_configure(padx=3, pady=3)
self.button = Button(self.frame, text='do Stuff', command=self.do_stuff)
self.button.grid(padx=3, pady=3)
self.progress = Progressbar(self.status_frame, orient="horizontal", length=80, mode="determinate")
self.progress.grid(row=1, column=0, pady=3, sticky='nesw')
self.grid()
# start ThreadedTask here
def do_stuff(self):
ThreadedTask(self.queue).start()
self.queue_process(DoStuffClass(ui=self))
def queue_process(self, process, retry_time=10):
self.master.after(retry_time, process)
def update_status(self):
self.parent.update_idletasks()
class DoStuffClass:
def __init__(self, ui=None):
self.ui = ui
self.long_task()
def long_task(self):
# do stuff here and update the progressbar from MyGui
self.ui.progress['maximum'] = 10000
# some lengthy task ...
for i in range(10000):
print(i)
self.ui.progress.step()
self.ui.parent.update_idletasks()
# main
root = Tk()
root.geometry("150x80+50+50")
MyGui(root)
root.mainloop()
root.quit()
我认为现在我的问题是队列和线程的错误实现,即
self.queue\u进程(dostufclass(ui=self))
。。。这种行为就像我根本不使用队列和多线程一样。progressbar可以工作,只要它保持“焦点”,这意味着我不点击桌面上的任何其他东西。只要我点击桌面上的其他地方,GUI就会失去焦点,GUI就会进入“无响应”模式,进度条不再更新。此外,有时Tcl关闭错误的线程,这会导致整个程序崩溃。因此,经过几次尝试后,我想出了该怎么办:
from tkinter import *
import queue
import threading
from tkinter.ttk import *
class ThreadedTask(object):
def __init__(self, parent):
self.parent = parent
self.queue = queue.Queue()
self.gui = MyGui(parent, self.queue)
self.work_queue()
def work_queue(self):
""" Check every 100 ms if there is something new in the queue. """
try:
self.parent.after(200, self.work_queue)
print('working queue with task {}...'.format(self.queue.get_nowait()))
except queue.Empty:
pass
# Gui class
class MyGui(Frame):
def __init__(self, parent, queue):
super().__init__(parent)
self.parent = parent
self.queue = queue
self.init_ui()
# define a button to fulfill long task
def init_ui(self):
self.frame = Frame(self, relief=RAISED, borderwidth=1)
self.frame.grid(row=0, column=0, columnspan=1, sticky='ns')
self.status_frame = Frame(self, relief=RAISED, borderwidth=1, height=20)
self.status_frame.grid(row=1, column=0, columnspan=3, sticky='nesw')
self.status_frame.grid_configure(padx=3, pady=3)
self.button = Button(self.frame, text='do Stuff', command=self.init_button_loop)
self.button.grid(padx=3, pady=3)
self.progress = Progressbar(self.status_frame, orient="horizontal", length=80, mode="determinate")
self.progress.grid(row=1, column=0, pady=3, sticky='nesw')
self.grid()
def start_thread(self, function_name, queue):
t = threading.Thread(target=function_name, args=(queue,))
# close thread automatically after finishing task
t.setDaemon(True)
t.start()
# execute button push by spawning a new thread
def init_button_loop(self):
self.start_thread(self.exec_button_loop, self.queue)
# execute long task
def exec_button_loop(self, queue):
self.progress['maximum'] = 10000
for i in range(10000):
# update progressbar
queue.put(self.progress.step())
# main
root = Tk()
root.geometry("150x80+50+50")
client = ThreadedTask(root)
root.mainloop()
困难在于在gui中按下按钮时,找出如何与队列和线程交互
基本上,我的错误是将队列声明在错误的类中,错误地使用了after
函数,而不知道从哪里开始线程
新的实现遵循在一个新线程中填充队列的原则,这个新线程是在按下gui按钮时产生的。从主线程定期检查队列是否有事情要做。这可以防止由于gui和主线程之间的安全通信而导致的无响应。您的代码过于精简。当我尝试运行它时:
AttributeError:“MyGui”对象没有来自行self.preview\u button.grid(padx=3,pady=3)的属性“preview\u button”
。顺便说一句,主线程是Python解释器首先执行的线程。您可能会发现它很有用。@martineau你好,martineau,我已经更新了代码。我测试了它,它一直工作到行self.queue\u进程(dostufclass(ui=self))
,这是我不确定的部分。但是,您确实看到progressbar在执行冗长的任务时移动,当您更改minigui的焦点时,它将进入无响应模式(对于我的原始程序,有时会崩溃)。您的问题非常广泛,并且有太多的子问题(其中一些没有意义,即没有子线程)。主线程是Python解释器开始运行的第一个代码。您可以在任何地方实现ThreadedTask
类。线程不是从它派生出来的,它的实例是独立的线程。需要记住的基本点是,只有一个线程(通常是主线程)应该更新GUI(通过after()
)。其他线程需要将内容放入队列
s,主线程应该检查它们并根据找到的内容更新GUI。您还可以查看.after()方法(在Tkinter中),该方法允许您在Tkinter中运行函数,而无需暂停GUI。这也将消除使用线程模块的需要。