繁体   English   中英

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

[英]How to correctly implement multithreading using a Tkinter GUI?

我正在尝试在tkinter中使用GUI时实现多线程。 我提出了这个解决方案 ,但是我没有实现它。

所以基本上我需要知道:

我该如何更改我的代码以使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)) ...行为与我根本不使用队列和多线程的情况相同。 只要进度栏保持“焦点对准”,它就会起作用,这意味着我不会单击桌面上的其他任何东西。 当我单击桌面上的其他位置并且GUI失去焦点时,GUI进入“无响应”模式,并且进度栏不再更新。 另外,有时Tcl关闭错误的Thread,这会使整个程序崩溃。

因此,经过几次尝试,我想出了办法:

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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM