如何使 Tkinter GUI 线程安全?

K-D*_*Doe 4 python performance multithreading canvas tkinter

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

我的问题是画布的重绘导致我的窗口/gui 每秒都滞后。我什至尝试在另一个线程中更新情节。但即使在那里我也会出现滞后。

有了我最新的代码,我的大部分工作就完成了。线程有助于防止我的 GUI/窗口在画布更新时冻结。

我最不想念的就是使其线程安全。

这是我收到的消息:

RuntimeError: main thread is not in main loop
Run Code Online (Sandbox Code Playgroud)

这是我最新的线程工作代码:

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()
Run Code Online (Sandbox Code Playgroud)

这是没有线程的想法:

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()
Run Code Online (Sandbox Code Playgroud)

这是我的数据库架构:

CREATE TABLE `Sensor_Stream_1` (
    `Date`  TEXT,
    `Temperature`   INTEGER,
    `Humidity`  INTEGER
);
Run Code Online (Sandbox Code Playgroud)

san*_*lot 5

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

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

主循环将如下所示:

running = True
while running:
    root.update()
    if data_available:
        copydata_to_gui()
root.quit()
Run Code Online (Sandbox Code Playgroud)