如何使用Tkinter GUI正确实现多线程?

2024-04-24 22:26:26 发布

您现在位置:Python中文网/ 问答频道 /正文

我尝试在tkinter中使用GUI时实现多线程。我来了this solution,但我没能实现它。你知道吗

所以基本上我需要知道:

我需要如何修改我的代码以使Progressbar友好而流畅地交互,即当GUI失去焦点时不进入无响应模式?

下面是我的代码:

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_process(DoStuffClass(ui=self))。。。这种行为就像我根本不使用队列和多线程一样。progressbar可以工作,只要它保持“焦点”,这意味着我不点击桌面上的任何其他东西。一旦我点击桌面上的其他地方,GUI就会失去焦点,GUI就会进入“无响应”模式,进度条不再更新。而且,有时Tcl关闭错误的线程,这会导致整个程序崩溃。你知道吗


Tags: importselfuitaskqueueinitdefstatus
1条回答
网友
1楼 · 发布于 2024-04-24 22:26:26

所以经过几次尝试,我想出了该怎么办:

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和主线程之间的安全通信而导致的无响应。你知道吗

相关问题 更多 >