PythonTkinter GUI从多个线程更新——它是线程安全的吗?一些示例代码

PythonTkinter GUI从多个线程更新——它是线程安全的吗?一些示例代码,python,multithreading,matplotlib,tkinter,thread-safety,Python,Multithreading,Matplotlib,Tkinter,Thread Safety,我一直在研究TKinter的一个问题,这是基于其他线程的数据更新GUI。正如许多人在网上建议的那样,我使用self.root.after()方法使用队列轮询策略 这工作得很好,但我有一个问题,我需要更新TKinter GUI中嵌入的matplotlib绘图,这样做会“停用”//将焦点从GUI的其他方面移开/可能会阻止GUI的其他方面。具体地说,如果我在TKinter条目中键入某些内容,并且matplotlib图形更新,则光标不再位于条目中,并且我的键入过程已中断 为了解决这个问题,我想我应该尝试

我一直在研究TKinter的一个问题,这是基于其他线程的数据更新GUI。正如许多人在网上建议的那样,我使用self.root.after()方法使用队列轮询策略

这工作得很好,但我有一个问题,我需要更新TKinter GUI中嵌入的matplotlib绘图,这样做会“停用”//将焦点从GUI的其他方面移开/可能会阻止GUI的其他方面。具体地说,如果我在TKinter条目中键入某些内容,并且matplotlib图形更新,则光标不再位于条目中,并且我的键入过程已中断

为了解决这个问题,我想我应该尝试在一个单独的线程中更新TKinter。现在,我在网上看到很多人声称TKinter不是线程安全的,但我也看到其他人说TKinter是线程安全的。我继续做了一个示例程序,它运行得很好:即使绘图更新,光标也可以保留在条目中。但我想知道的是,这个程序安全吗?或者它容易受到随机或可预测的失败的影响?我能做些什么来拥有线程安全的GUI

下面是我的示例代码:

import Tkinter as tk
import threading
import Queue
import datetime
import math
import matplotlib
matplotlib.use("TkAgg")
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg, NavigationToolbar2TkAgg
from matplotlib.figure import Figure
import matplotlib.gridspec as gridspec

gui_queue = Queue.Queue()

GUI_THEME_COLOR = 'lightblue'

##################################################
class MainGUI:
    def __init__(self, root):
        self.root = root
        self.root.title('TKinter GUI with Threaded Updates')
        self.root.minsize(width=800, height=300)
        self.root.config(background=GUI_THEME_COLOR)

        self.big_frame = tk.Frame(master=root)
        self.big_frame.pack(fill=tk.BOTH, expand=tk.YES, padx=10, pady=10)

        self.button1 = tk.Button(master=self.big_frame, text='Button 1', command=self.button1_command)
        self.button1.grid(row=0, column=0)

        self.entry1 = tk.Entry(master=self.big_frame)
        self.entry1.bind('<Return>', self.entry1_event)
        self.entry1.grid(row=1, column=0)

    def entry1_event(self, event):
        self.button1.config(text=str(self.entry1.get()))
        self.entry1.delete(0, tk.END)

    def button1_command(self):
        print 'Button 1 clicked'


class GUIManipulator(threading.Thread):
    def __init__(self):
        threading.Thread.__init__(self)
        self.app = None
        self.start_time = datetime.datetime.utcnow()
        self.sec_checker = 1
        self.num_loops = 0
        self.x_list = []
        self.y_list = []

    def run(self):
        print 'Starting GUIManipulator thread...'
        while gui_queue.empty():    # Wait here until we receive the MainGUI instance
            pass
        self.app = gui_queue.get()
        while True:
            diff_time = (datetime.datetime.utcnow() - self.start_time).total_seconds()
            floor_diff_time = math.floor(diff_time)
            if floor_diff_time >= self.sec_checker:     # Configure button1 text every second
                self.app.button1.config(text=str(floor_diff_time))
                self.sec_checker += 1
                self.plot_figure()

    def plot_figure(self):
        self.num_loops += 1
        self.x_list.append(self.num_loops)
        self.y_list.append(math.sin(self.num_loops/5.0))

        if self.num_loops == 1:
            self.fig1 = Figure(figsize=(12, 6), facecolor=GUI_THEME_COLOR)
            self.fig1.suptitle('Figure 1',
                                fontsize=14,
                                fontweight='bold')

            self.gs = gridspec.GridSpec(2, 2)

            self.plot1 = self.fig1.add_subplot(self.gs[:, 0])
            self.plot1.set_title('Plot 1')
            self.plot1.set_xlabel('x')
            self.plot1.set_ylabel('sin(x)', labelpad=-10)
            self.plot1.grid(True)

        if self.num_loops > 1:
            self.plot1.cla()
        self.plot1.plot(self.x_list, self.y_list)

        if self.num_loops == 1:
            self.canvas = FigureCanvasTkAgg(self.fig1, master=self.app.big_frame)
            self.canvas.draw()
            self.canvas.get_tk_widget().grid(row=2, column=0)
        else:
            self.canvas.draw()


def main():
    root = tk.Tk()
    app = MainGUI(root)

    gui_queue.put(app)

    gui_manipulator_thread = GUIManipulator()
    gui_manipulator_thread.daemon = True
    gui_manipulator_thread.start()

    root.mainloop()


if __name__ == '__main__':
    main()
将Tkinter作为tk导入
导入线程
导入队列
导入日期时间
输入数学
导入matplotlib
matplotlib.use(“TkAgg”)
从matplotlib.backends.backend_tkagg导入图CAVASTKAGG,导航工具栏2TKAGG
从matplotlib.figure导入图形
将matplotlib.gridspec导入为gridspec
gui_queue=queue.queue()
GUI_主题_颜色='浅蓝色'
##################################################
类MainGUI:
定义初始化(自,根):
self.root=根
self.root.title('带有线程更新的TKinter GUI')
self.root.minsize(宽度=800,高度=300)
self.root.config(background=GUI\u THEME\u COLOR)
self.big_frame=tk.frame(master=root)
self.big_frame.pack(fill=tk.BOTH,expand=tk.YES,padx=10,pady=10)
self.button1=tk.Button(master=self.big\u框架,text='Button 1',command=self.button1\u命令)
self.button1.grid(行=0,列=0)
self.entry1=tk.Entry(master=self.big\u框架)
self.entry1.bind(“”,self.entry1\u事件)
self.entry1.grid(行=1,列=0)
def入口1_事件(自身、事件):
self.button1.config(text=str(self.entry1.get())
self.entry1.delete(0,tk.END)
def按钮1_命令(自身):
打印“单击按钮1”
类GUI操纵器(threading.Thread):
定义初始化(自):
threading.Thread.\uuuuu init\uuuuuu(自)
self.app=无
self.start\u time=datetime.datetime.utcnow()
self.sec_checker=1
self.num_循环=0
self.x_list=[]
self.y_list=[]
def运行(自):
打印“起始线程…”
while gui_queue.empty():#在这里等待,直到我们收到MainGUI实例
通过
self.app=gui_queue.get()
尽管如此:
diff_time=(datetime.datetime.utcnow()-self.start_time)。总秒数()
楼层差异时间=数学楼层(差异时间)
如果floor_diff_time>=self.sec_checker:#每秒配置按钮1文本
self.app.button1.config(text=str(楼层差异时间))
self.sec_checker+=1
self.plot_图()
def plot_图(自身):
self.num_循环+=1
self.x_list.append(self.num_循环)
self.y_list.append(math.sin(self.num_循环/5.0))
如果self.num_循环==1:
self.fig1=Figure(figsize=(12,6),facecolor=GUI\u THEME\u COLOR)
self.fig1.suptitle('图1',
fontsize=14,
fontwweight='bold')
self.gs=gridspec.gridspec(2,2)
self.plot1=self.fig1.add_子图(self.gs[:,0])
self.plot1.set_title('Plot 1')
self.plot1.set_xlabel('x')
self.plot1.set_ylabel('sin(x'),labelpad=-10)
self.plot1.grid(真)
如果self.num_循环>1:
self.plot1.cla()
self.plot1.plot(self.x\u列表、self.y\u列表)
如果self.num_循环==1:
self.canvas=FigureCanvasTkAgg(self.fig1,master=self.app.big_frame)
self.canvas.draw()
self.canvas.get_tk_widget().grid(行=2,列=0)
其他:
self.canvas.draw()
def main():
root=tk.tk()
app=MainGUI(根目录)
gui_queue.put(应用程序)
gui\u操纵器\u线程=gui操纵器()
gui\u操纵器\u thread.daemon=True
gui_操纵器_线程.start()
root.mainloop()
如果uuuu name uuuuuu='\uuuuuuu main\uuuuuuu':
main()
提前谢谢

可能重复的