如何使Tkinter GUI线程安全? [英] How to make Tkinter GUI thread safe?

查看:84
本文介绍了如何使Tkinter GUI线程安全?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我写了一段代码,其中有一个带有画布的简单GUI.在此画布上,我绘制了一个Matplot. Matplot每秒都会更新一次SQ Lite数据库中的数据,我在其中填充了一些虚假的Sensor信息(目前仅用于测试).

I have written a piece of code where I have a simple GUI with an canvas. On this canvas I draw a Matplot. The Matplot is updated every second with data from an SQ Lite DB which I fill with some fake Sensor information (just for testing at the moment).

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

My Problem was that the redrawing of the canvas causes my window/gui to lag every second. I even tried to update the plot in another thread. But even there I get an lag.

使用最新的代码,我可以完成大部分工作.线程有助于防止Canvas更新时我的GUI/窗口冻结.

With my newest Code i got most of my things working. Threading helps to prevent my GUI/Window from freezing while the Canvas is updated.

我最想念的就是使它成为线程安全.

这是我收到的消息:

RuntimeError: main thread is not in main loop

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

Here is my newest working code with threading:

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进程不得在任何线程中运行.只有数据采集必须是线程化的.

Your GUI process must not run in any thread. only the dataacquisition must be threaded.

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

When needed , the data acquired are transfered to the gui process (or the gui process notified from new data available) . I may need to use a mutex to share data resource between acquisition thread and gui (when copying)

主循环看起来像:

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

这篇关于如何使Tkinter GUI线程安全?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

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