Tkinter:如何使用线程来防止主事件循环“冻结" [英] Tkinter: How to use threads to preventing main event loop from "freezing"

查看:28
本文介绍了Tkinter:如何使用线程来防止主事件循环“冻结"的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有一个带有开始"按钮和进度条的小型 GUI 测试.所需的行为是:

I have a small GUI test with a "Start" button and a Progress bar. The desired behavior is:

  • 点击开始
  • 进度条振荡 5 秒
  • 进度条停止

观察到的行为是开始"按钮冻结 5 秒钟,然后显示进度条(无振荡).

The observed behavior is the "Start" button freezes for 5 seconds, then a Progressbar is displayed (no oscillation).

这是我目前的代码:

class GUI:
    def __init__(self, master):
        self.master = master
        self.test_button = Button(self.master, command=self.tb_click)
        self.test_button.configure(
            text="Start", background="Grey",
            padx=50
            )
        self.test_button.pack(side=TOP)

    def progress(self):
        self.prog_bar = ttk.Progressbar(
            self.master, orient="horizontal",
            length=200, mode="indeterminate"
            )
        self.prog_bar.pack(side=TOP)

    def tb_click(self):
        self.progress()
        self.prog_bar.start()
        # Simulate long running process
        t = threading.Thread(target=time.sleep, args=(5,))
        t.start()
        t.join()
        self.prog_bar.stop()

root = Tk()
root.title("Test Button")
main_ui = GUI(root)
root.mainloop()

基于来自 Bryan Oakley 此处,我知道我需要使用线程.我尝试创建一个线程,但我猜因为该线程是从主线程中启动的,所以它没有帮助.

Based on the information from Bryan Oakley here, I understand that I need to use threads. I tried creating a thread, but I'm guessing that since the thread is started from within the main thread, it doesn't help.

我的想法是将逻辑部分放在不同的类中,并从该类中实例化 GUI,类似于 A. Rodas 的示例代码 此处.

I had the idea to place the logic portion in a different class, and instantiate the GUI from within that class, similar to the example code by A. Rodas here.

我的问题:

我不知道如何编码它以便此命令:

I can't figure out how to code it so that this command:

self.test_button = Button(self.master, command=self.tb_click)

调用位于另一个类中的函数.这是一件坏事还是有可能?我将如何创建可以处理 self.tb_click 的第二个类?我尝试遵循 A. Rodas 的示例代码,它运行良好.但我无法弄清楚如何在触发操作的 Button 小部件的情况下实现他的解决方案.

calls a function that is located in the other class. Is this a Bad Thing to do or is it even possible? How would I create a 2nd class that can handle the self.tb_click? I tried following along to A. Rodas' example code which works beautifully. But I cannot figure out how to implement his solution in the case of a Button widget that triggers an action.

如果我应该在单个 GUI 类中处理线程,如何创建一个不干扰主线程的线程?

If I should instead handle the thread from within the single GUI class, how would one create a thread that doesn't interfere with the main thread?

推荐答案

当你在主线程中加入新线程时,它会一直等到线程结束,所以即使你在使用多线程,GUI 也会阻塞.

When you join the new thread in the main thread, it will wait until the thread finishes, so the GUI will block even though you are using multithreading.

>

如果要将逻辑部分放在不同的类中,可以直接子类化Thread,然后按下按钮启动该类的新对象.Thread 的这个子类的构造函数可以接收一个 Queue 对象,然后您就可以将它与 GUI 部分进行通信.所以我的建议是:

If you want to place the logic portion in a different class, you can subclass Thread directly, and then start a new object of this class when you press the button. The constructor of this subclass of Thread can receive a Queue object and then you will be able to communicate it with the GUI part. So my suggestion is:

  1. 在主线程中创建一个Queue对象
  2. 创建一个可以访问该队列的新线程
  3. 定期检查主线程中的队列

然后你必须解决如果用户点击同一个按钮两次会发生什么的问题(每次点击都会产生一个新线程),但是你可以通过禁用开始按钮并在你之后再次启用它来修复它调用 self.prog_bar.stop().

Then you have to solve the problem of what happens if the user clicks two times the same button (it will spawn a new thread with each click), but you can fix it by disabling the start button and enabling it again after you call self.prog_bar.stop().

import queue

class GUI:
    # ...

    def tb_click(self):
        self.progress()
        self.prog_bar.start()
        self.queue = queue.Queue()
        ThreadedTask(self.queue).start()
        self.master.after(100, self.process_queue)

    def process_queue(self):
        try:
            msg = self.queue.get_nowait()
            # Show result of the task if needed
            self.prog_bar.stop()
        except queue.Empty:
            self.master.after(100, self.process_queue)

class ThreadedTask(threading.Thread):
    def __init__(self, queue):
        super().__init__()
        self.queue = queue
    def run(self):
        time.sleep(5)  # Simulate long running process
        self.queue.put("Task finished")

这篇关于Tkinter:如何使用线程来防止主事件循环“冻结"的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆