python-3.x 使用线程从用户输入更新tkinter中的图而没有延迟

nhaq1z21  于 2023-05-19  发布在  Python
关注(0)|答案(1)|浏览(195)

我现在正在编写我的第一个tkinter GUI。我试图使一个交互式的情节,使用一些规模,使用户可以设置影响情节的参数值。当我这样做的时候,它开始滞后,我的解决方案;使用线程没有按预期工作。
在我的真实的代码中,有多个图,因此应用程序开始滞后,如下所示:

from tkinter import *
from tkinter import ttk

import threading

import matplotlib.pyplot as plt
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg

import time

import numpy as np

def create_window(root):
    frame1 = Frame(root)
    frame2 = Frame(root)
    frame1.grid(row=0,column=0)
    frame2.grid(row=0,column=1)

    figure1 = plt.Figure(figsize=(5,5))
    figure1.set_tight_layout(True)
    
    ax1 = figure1.add_subplot(111)
    canvas1 = FigureCanvasTkAgg(figure1, frame1)
    canvas1.get_tk_widget().grid(column=1,row=1, padx=10, pady=10)

    m.trace_add('write', lambda var=None, index=None, mode=None: update_plot_tracer(ax1, canvas1))

    m_scale = ttk.Scale(frame2, orient=VERTICAL, length=200, from_=100.0, to=0.0, variable=m)
    m_scale.grid(column=0, row=0)
    update_plot(ax1,canvas1)

def update_plot(ax, canvas):
    #to make it lag
    time.sleep(0.1)

    x = np.arange(0,10,1)
    y = m.get() * x
    ax.clear()
    ax.plot(x,y)
    ax.set_ylim([0, 100])
    canvas.draw()
def update_plot_tracer(ax, canvas, var=None, index=None, mode=None):
    update_plot(ax, canvas)

if __name__ == '__main__':

    root = Tk()
    m = DoubleVar(value = 10.0)
    create_window(root)

    root.mainloop()

我的解决方案是使用线程,以便在绘图时用户界面的其余部分仍然可用。我现在遇到的问题是,当用户拖动刻度很多时,会调用多个线程,这些线程会相互干扰,所以我试图阻止程序同时多次更新,如下所示:

from tkinter import *
from tkinter import ttk

import threading

import matplotlib.pyplot as plt
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg

import time

import numpy as np

def create_window(root):
    frame1 = Frame(root)
    frame2 = Frame(root)
    frame1.grid(row=0,column=0)
    frame2.grid(row=0,column=1)

    figure1 = plt.Figure(figsize=(5,5))
    figure1.set_tight_layout(True)
    
    ax1 = figure1.add_subplot(111)
    canvas1 = FigureCanvasTkAgg(figure1, frame1)
    canvas1.get_tk_widget().grid(column=1,row=1, padx=10, pady=10)

    m.trace_add('write', lambda var=None, index=None, mode=None: update_plot_tracer(ax1, canvas1))

    m_scale = ttk.Scale(frame2, orient=VERTICAL, length=200, from_=100.0, to=0.0, variable=m)
    m_scale.grid(column=0, row=0)
    update_plot(ax1,canvas1)

    m_entry = ttk.Entry(frame2, textvariable = m)
    m_entry.grid(column=0, row=1)

def update_plot(ax, canvas):
    
    x = np.arange(0,10,1)
    y = m.get() * x
    print('last used value of m', m.get())
    ax.clear()
    ax.plot(x,y)
    ax.set_ylim([0, 100])
    canvas.draw()
    #to make it lag
    time.sleep(0.2)

def update_plot_tracer(ax, canvas, var=None, index=None, mode=None):
    global thread1#just to make this example work, in my real code i have it as a class variable...
    if thread1 is None or not thread1.is_alive():
        thread1 = threading.Thread(target= lambda: update_plot(ax, canvas))
        thread1.start()
        
if __name__ == '__main__':
    thread1 = None
    root = Tk()
    
    m = DoubleVar(value = 10.0)
    create_window(root)

    root.mainloop()

现在我有了下一个问题:当线程启动但用户继续移动鼠标时,他/她看到的值不是图中使用的值,所以我想做一些事情,比如将线程添加到队列中,但然后我会遇到这个队列可能变得很长并且需要一些时间来更新到最新状态的问题,即使当前运行和最新添加之间的所有线程都是无用的,但据我所知,一旦我向队列中添加了一个线程(从队列bib),它就不能被删除。我从来没有与大部分这方面的工作,所以任何帮助是赞赏。
此外,我希望尽可能保持它的交互性,所以只有在用户放开秤后才更新不是我喜欢的方式。
编辑:问题是我如何创建一个线程队列,我可以从中删除条目?

iqih9akk

iqih9akk1#

尽管Tkinter支持在不同于创建Tcl解释器的线程(根Tk示例)的线程中操作GUI,但最好避免这种情况,因为实现并不完美,大多数GUI框架都不支持这种情况。我建议在后台线程中进行数据处理,在主线程中进行绘图。
现在,让我们转到队列的主要主题。您不需要实现线程队列。你可以在现有线程结束后再启动一个新线程。使用after_idle()来调度这样的函数。另外,不要忘记加入线程。

...
def update_plot_tracer(ax, canvas, var=None, index=None, mode=None):
    global thread1
    if thread1 is None:
        def entry():
            update_plot(ax, canvas)
            root.after_idle(on_end_thread)
        def on_end_thread(): # This will be run in the main thread.
            global thread1
            if thread1.invalidated:
                root.after_idle(lambda: update_plot_tracer(ax, canvas))
            thread1.join()
            thread1 = None
        thread1 = threading.Thread(target=entry)
        thread1.invalidated = False
        thread1.start()
    else:
        thread1.invalidated = True
...

相关问题