Skip to main content
 首页 » 编程设计

python之如何使 Tkinter GUI 线程安全

2024年08月14日30hnrainll

我写了一段代码,其中有一个带 Canvas 的简单 GUI。在这个 Canvas 上我画了一个 Matplot。 Matplot 每秒更新一次来自 SQ Lite DB 的数据,我填充了一些虚假的传感器信息(目前仅用于测试)。

我的问题是 Canvas 的重绘导致我的窗口/GUI 每秒都滞后。我什至试图在另一个线程中更新情节。但即使在那里我也有滞后。

使用我的最新代码,我的大部分工作都正常进行了。线程有助于防止我的 GUI/窗口在更新 Canvas 时卡住。

我想念的最后一件事是让它成为线程安全的。

这是我收到的消息:

RuntimeError: main thread is not in main loop 

这是我最新的线程代码:

from tkinter import * 
import random 
from random import randint  
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg 
from matplotlib.figure import Figure 
import time 
import threading 
from datetime import datetime 
 
continuePlotting = False 
 
def change_state(): 
    global continuePlotting 
    if continuePlotting == True: 
        continuePlotting = False 
    else: 
        continuePlotting = True     
 
def data_points(): 
    yList = [] 
    for x in range (0, 20): 
        yList.append(random.randint(0, 100)) 
 
    return yList 
 
def app(): 
    # initialise a window and creating the GUI 
    root = Tk() 
    root.config(background='white') 
    root.geometry("1000x700") 
 
    lab = Label(root, text="Live Plotting", bg = 'white').pack() 
 
    fig = Figure() 
 
    ax = fig.add_subplot(111) 
    ax.set_ylim(0,100) 
    ax.set_xlim(1,30) 
    ax.grid() 
 
    graph = FigureCanvasTkAgg(fig, master=root) 
    graph.get_tk_widget().pack(side="top",fill='both',expand=True) 
 
    # Updated the Canvas  
    def plotter(): 
        while continuePlotting: 
            ax.cla() 
            ax.grid() 
            ax.set_ylim(0,100) 
            ax.set_xlim(1,20) 
 
            dpts = data_points() 
            ax.plot(range(20), dpts, marker='o', color='orange') 
            graph.draw() 
            time.sleep(1) 
 
    def gui_handler(): 
        change_state() 
        threading.Thread(target=plotter).start() 
 
    b = Button(root, text="Start/Stop", command=gui_handler, bg="red", fg="white") 
    b.pack() 
 
    root.mainloop() 
 
if __name__ == '__main__': 
    app() 

这里没有线程的想法:

from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg 
from matplotlib.figure import Figure 
import tkinter as tk 
import numpy as np 
import matplotlib.pyplot as plt 
import matplotlib.animation as animation 
import sqlite3 
from datetime import datetime 
from random import randint 
 
class MainApplication(tk.Frame): 
    def __init__(self, parent, *args, **kwargs): 
        tk.Frame.__init__(self, parent, *args, **kwargs) 
        self.parent = parent 
 
        root.update_idletasks() 
 
        f = Figure(figsize=(5,5), dpi=100)         
        x=1 
        ax = f.add_subplot(111)         
        line = ax.plot(x, np.sin(x))         
 
        def animate(i): 
            # Open Database 
            conn = sqlite3.connect('Sensor_Data.db') 
            c = conn.cursor() 
            # Create some fake Sensor Data     
            NowIs = datetime.now() 
            Temperature = randint(0, 100) 
            Humidity = randint(0, 100) 
            # Add Data to the Database 
            c = conn.cursor() 
            # Insert a row of data 
            c.execute("insert into Sensor_Stream_1 (Date, Temperature, Humidity) values (?, ?, ?)", 
                        (NowIs, Temperature, Humidity)) 
            # Save (commit) the changes 
            conn.commit() 
            # Select Data from the Database 
            c.execute("SELECT Temperature FROM Sensor_Stream_1 LIMIT 10 OFFSET (SELECT COUNT(*) FROM Sensor_Stream_1)-10")  
            # Gives a list of all temperature values  
            x = 1 
            Temperatures = [] 
 
            for record in c.fetchall():     
                Temperatures.append(str(x)+','+str(record[0])) 
                x+=1 
            # Setting up the Plot with X and Y Values 
            xList = [] 
            yList = [] 
 
            for eachLine in Temperatures: 
                if len(eachLine) > 1: 
                    x, y = eachLine.split(',') 
                    xList.append(int(x)) 
                    yList.append(int(y)) 
 
            ax.clear() 
 
            ax.plot(xList, yList)  
 
            ax.set_ylim(0,100) 
            ax.set_xlim(1,10) 
            ax.grid(b=None, which='major', axis='both', **kwargs) 
 
 
        label = tk.Label(root,text="Temperature / Humidity").pack(side="top", fill="both", expand=True) 
 
        canvas = FigureCanvasTkAgg(f, master=root) 
        canvas.get_tk_widget().pack(side="left", fill="both", expand=True) 
 
        root.ani = animation.FuncAnimation(f, animate, interval=1000)             
 
if __name__ == "__main__": 
    root = tk.Tk() 
    MainApplication(root).pack(side="top", fill="both", expand=True) 
    root.mainloop() 

这是我的数据库架构:

CREATE TABLE `Sensor_Stream_1` ( 
    `Date`  TEXT, 
    `Temperature`   INTEGER, 
    `Humidity`  INTEGER 
); 

请您参考如下方法:

您的 GUI 进程不得在任何线程中运行。只有数据采集必须是线程化的。

当需要时,将获取的数据传输到 gui 进程(或从新数据可用通知的 gui 进程)。我可能需要使用互斥锁在获取线程和 gui 之间共享数据资源(复制时)

主循环看起来像:

running = True 
while running: 
    root.update() 
    if data_available: 
        copydata_to_gui() 
root.quit()