Tkinter:如何使用多线程在后台保持监视功能处于活动状态

Tkinter: How use multithreading to keep a monitoring function active in the background

我正在制作一个 GUI,用于在超级计算机上可视化 SLURM 队列。我想通过 ssh(使用 paramiko)定期发送命令来持续监控队列中 运行 和待处理作业的数量,以获取队列、分析它并更新一些与 tk 标签相关的 tk 变量。

到目前为止,我一直在使用 self.after() 语法来执行此操作,并确保不要过于频繁地进行后台 SSH 调用(以免导致界面出现恼人的死机)。然而,我正在重写我的代码,我也想监控其他几个方面,但这会大大降低界面速度。

我认为这可以通过使用 threading 模块来解决,但我不确定如何正确设置它。为了这个问题,考虑以下MWE:

import tkinter as tk


class GUI(tk.Tk):
    def __init__(self):
        tk.Tk.__init__(self)

        Home(self)


class Home(tk.Frame):
    def __init__(self, parent):
        tk.Frame.__init__(self, parent)
        self.parent = parent
        self.counter = tk.IntVar()
        self.counter2 = tk.IntVar()
        self.grid(row=0, column=0)

        tk.Label(self, text='Counter 1:').grid(row=0, column=0)
        tk.Label(self, textvar=self.counter).grid(row=0, column=1)

        tk.Label(self, text='Counter 2:').grid(row=1, column=0)
        tk.Label(self, textvar=self.counter2).grid(row=1, column=1)

        self.monitor_counter()
        self.monitor_counter2()

    def monitor_counter(self, end=100, delay=1000):
        time.sleep(0.2)
        if self.counter.get() < 0:
            self.parent.destroy()
        self.counter.set(end)
        self.after(int(delay), lambda: self.monitor_counter(end-1))

    def monitor_counter2(self, start=0, delay=1000):
        time.sleep(0.2)
        self.counter2.set(start)
        self.after(int(delay), lambda: self.monitor_counter2(start+1))


if __name__ == '__main__':
    GUI().mainloop()

它只是跟踪连接到某些标签的变化 IntVars,使用 sleep 语句来模拟 SSH 调用。现在的设置方式,尽管更新了计数器,但 GUI 不显示。

我如何修改上面的程序以将监视函数作为它们自己的线程生成?假设监控应该始终处于活动状态,并且用户不能在不退出程序的情况下停用它们。此外,当退出程序时调用 root.destroy() 时,当然应该终止线程。

下面是基于您使用线程的更新代码:

import tkinter as tk
import time
import threading

class GUI(tk.Tk):
    def __init__(self):
        tk.Tk.__init__(self)
        Home(self)

class Home(tk.Frame):
    def __init__(self, parent):
        tk.Frame.__init__(self, parent)
        self.parent = parent
        self.counter = tk.IntVar()
        self.counter2 = tk.IntVar()
        self.grid(row=0, column=0)

        tk.Label(self, text='Counter 1:').grid(row=0, column=0)
        tk.Label(self, textvar=self.counter).grid(row=0, column=1)

        tk.Label(self, text='Counter 2:').grid(row=1, column=0)
        tk.Label(self, textvar=self.counter2).grid(row=1, column=1)

        # start the monitor tasks
        threading.Thread(target=self.monitor_counter, daemon=True).start()
        threading.Thread(target=self.monitor_counter2, daemon=True).start()

    def monitor_counter(self, end=100, delay=1):
        while True:
            time.sleep(0.2) # simulate SSH call
            if self.counter.get() < 0:
                self.parent.destroy()
            end -= 1
            self.counter.set(end)
            time.sleep(delay)

    def monitor_counter2(self, start=0, delay=1):
        while True:
            time.sleep(0.2) # simulate SSH call
            self.counter2.set(start)
            start += 1
            time.sleep(delay)

if __name__ == '__main__':
    GUI().mainloop()