Warning: file_get_contents(/data/phpspider/zhask/data//catemap/6/multithreading/4.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Python 如何使Tkinter GUI线程安全?_Python_Multithreading_Performance_Canvas_Tkinter - Fatal编程技术网

Python 如何使Tkinter GUI线程安全?

Python 如何使Tkinter GUI线程安全?,python,multithreading,performance,canvas,tkinter,Python,Multithreading,Performance,Canvas,Tkinter,我已经编写了一段代码,其中有一个简单的GUI和画布。在这张画布上我画了一个Matplot。Matplot每秒更新一次来自SQ-Lite数据库的数据,我用一些虚假的传感器信息填充这些数据(只是为了目前的测试) 我的问题是画布的重画导致我的窗口/gui每秒都会延迟。我甚至尝试在另一个线程中更新情节。但即使在那里,我也有滞后 用我最新的代码,我的大部分东西都能正常工作。线程有助于防止画布更新时GUI/窗口冻结 我错过的最后一件事是确保线程安全。 这是我得到的信息: RuntimeError: main

我已经编写了一段代码,其中有一个简单的GUI和画布。在这张画布上我画了一个Matplot。Matplot每秒更新一次来自SQ-Lite数据库的数据,我用一些虚假的传感器信息填充这些数据(只是为了目前的测试)

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

用我最新的代码,我的大部分东西都能正常工作。线程有助于防止画布更新时GUI/窗口冻结

我错过的最后一件事是确保线程安全。

这是我得到的信息:

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()

此函数每秒调用一次,超出正常刷新范围

def start(self,parent):
    self.close=False
    self.Refresh(parent)

def Refresh(self,parent):
    '''your code'''
    if(self.close == False):
        frame.after( UpdateDelay*1000, self.Refresh, parent)

该函数是单独调用的,它内部发生的一切都不会阻止接口的正常运行。

我在tkinter上也遇到了同样的问题,使用事件是我的解决方案。 如上所述,您必须在另一个线程中运行计算,然后将其发送到gui线程

import time
import tkinter as tk
import threading
from pubsub import pub

lock = threading.Lock()


class MainApplication(tk.Frame):
    def __init__(self, parent, *args, **kwargs):
        tk.Frame.__init__(self, parent, *args, **kwargs)
        self.parent = parent
        self.label = tk.Label(root, text="Temperature / Humidity")
        self.label.pack(side="top", fill="both", expand=True)

    def listener(self, plot_data):
        with lock:
            """do your plot drawing things here"""
            self.label.configure(text=plot_data)


class WorkerThread(threading.Thread):
    def __init__(self):
        super(WorkerThread, self).__init__()
        self.daemon = True  # do not keep thread after app exit
        self._stop = False

    def run(self):
        """calculate your plot data here"""    
        for i in range(100):
            if self._stop:
                break
            time.sleep(1)
            pub.sendMessage('listener', text=str(i))


if __name__ == "__main__":
    root = tk.Tk()
    root.wm_geometry("320x240+100+100")

    main = MainApplication(root)
    main.pack(side="top", fill="both", expand=True)

    pub.subscribe(main.listener, 'listener')

    wt = WorkerThread()
    wt.start()

    root.mainloop()

我将构建一个单独的类来处理绘制数据,然后在自己的线程中运行画布时将画布传递给该类。这将防止您看到的延迟。也就是说,我以前使用过matplotlib,并且没有延迟问题,因此您的代码中可能存在导致延迟的内容。您有任何示例吗?我以前从未使用过线程。这里有许多关于堆栈溢出的线程示例。但我现在正在查看您的代码,看看是否有什么突出的地方可能导致此延迟。好的。谢谢您查看我的代码。@Mike SMT我编辑了我的代码。尝试使用多线程。似乎不太管用。