gpt4 book ai didi

python - Tkinter GUI,I/O和线程: When to use queues,什么时候发生?

转载 作者:行者123 更新时间:2023-12-03 13:23:22 26 4
gpt4 key购买 nike

我正在使用TKinter来构建GUI(用于与多 channel 分析仪的套接字连接),以按固定间隔(〜15秒)接收和绘制数据(〜15.000.000值)。
在接收数据时,我不希望GUI冻结,因此我正在使用多线程进行连接处理,数据接收和绘图操作。如可重现的代码所示,我通过用threading.Event()设置了一个事件并依次处理一个线程(initSettings()acquireAndPlotData中的几行代码)来实现这一点。我唯一一次干扰GUI的时候是在绘制到 Canvas 上时,我使用tkinters after()方法执行此操作。
启动后,只要打开窗口并按预期工作,代码便会在不冻结的情况下运行并接收并绘图。
在阅读有关在tkinter GUI中处理阻塞I/O操作的文章时,我仅发现了一些示例,这些示例以递归方式对队列进行排队和检查(使用Queueafter()
1
2
3
4
5
),但我发现使用threading.Event()处理这些操作更加便捷。
现在我的问题是:
我在使用正确的方法还是在这里遗漏了重要的东西? (关于线程安全性,竞争条件,如果绘制失败并且花费的时间比获取数据的时间长,该怎么办?我没想到的事情?不良做法?等)
我真的很感谢您对此事的反馈!
可复制的代码

#####################*** IMPORTS ***#######################################################
import tkinter
from tkinter import ttk

from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
from matplotlib.figure import Figure

import time
import threading

import numpy as np

################### *** FUNCTIONS *** #########################################################
# *** initializes two threads for initializing connection & receiving/plotting data ***
def onStartButtonClick(event):
#
init_settings_thread.start()
acquire_and_plot_data_thread.start()
#

# *** inizialize connection & set event when finished & ready for sending data ***
def initSettings():
#time.sleep() simulates the time it takes to inizialize the connection
time.sleep(2)
start_data_acquisition_event.set()

# *** waiting for event/flag from initSettings() & start data receiving/plotting loop afer event set ***
def acquireAndPlotData():
start_data_acquisition_event.wait()
while start_data_acquisition_event.is_set():
# time.sleep() simulates the time it takes the connection to fill up the buffer
time.sleep(4)
# send updateGuiFigure to tkinters event queue, so that it won't freeze
root.after(0, updateGuiFigure)

# *** set new data points on existing plot & blit GUI canvas ***
def updateGuiFigure():
# simulate data -> 15.000.000 points in real application
line.set_xdata(np.random.rand(10))
#
line.set_ydata(np.random.rand(10))
#
plotting_canvas.restore_region(background) # restore background
ax.draw_artist(line) # redraw just the line -> draw_artist updates axis
plotting_canvas.blit(ax.bbox) # fill in the axes rectangle
#

# *** update background for resize events ***
def update_background(event):
global background
background = plotting_canvas.copy_from_bbox(ax.bbox)

##########################*** MAIN ***#########################################################

# Init GUI
root = tkinter.Tk()

# Init frame & canvas
frame = ttk.Frame(root)
plotting_area = tkinter.Canvas(root, width=700, height=400)
#
frame.grid(row=0, column=1, sticky="n")
plotting_area.grid(row=0, column=0)

# Init button & bind to function onStartButtonClick
start_button = tkinter.Button(frame, text="Start")
start_button.bind("<Button-1>", onStartButtonClick)
start_button.grid(row=0, column=0)

# Init figure & axis
fig = Figure(figsize=(7, 4), dpi=100)
ax = fig.add_subplot(111)

# Connect figure to plotting_area from GUI
plotting_canvas = FigureCanvasTkAgg(fig, master=plotting_area)

# Set axis
ax.set_title('Test')
ax.grid(True)
ax.set_xlabel('x-axis')
ax.set_ylabel('y-axis')
ax.set(xlim=[0,1], ylim=[0, 1])

# Init plot
line, = ax.plot([], [])
# if animated == True: artist (= line) will only be drawn when manually called draw_artist(line)
line.set_animated(True)

# Draw plot to GUI canvas
plotting_canvas.draw()
plotting_canvas.get_tk_widget().pack(fill=tkinter.BOTH)
background = plotting_canvas.copy_from_bbox(ax.bbox) # cache background
plotting_canvas.mpl_connect('draw_event', update_background) # update background with 'draw_event'

# Init threads
start_data_acquisition_event = threading.Event()
#
init_settings_thread = threading.Thread(name='init_settings_thread', target=initSettings, daemon=True)
acquire_and_plot_data_thread = threading.Thread(name='acquire_and_plot_data_thread', target=acquireAndPlotData, daemon=True)

# Start tkinter mainloop
root.mainloop()
截取多个类的代码示例如下所示(与上面的代码相同,但不可重现,可以忽略):
def onStartButtonClick(self):
#
.
# Disable buttons and get widget values here etc.
.
#
self.start_data_acquisition_event = threading.Event()
self.init_settings_thread = threading.Thread(target=self.initSettings)
self.acquire_and_plot_data_thread = threading.Thread(target=self.acquireAndPlotData)
#
self.init_settings_thread.start()
self.acquire_and_plot_data_thread.start()
# FUNCTION END

def initSettings(self):
self.data_handler.setInitSettings(self.user_settings_dict)
self.data_handler.initDataAcquisitionObject()
self.start_data_acquisition_event.set()

def acquireAndPlotData(self):
self.start_data_acquisition_event.wait()
while self.start_data_acquisition_event.is_set():
self.data_handler.getDataFromDataAcquisitionObject()
self.master.after(0, self.data_plotter.updateGuiFigure)

最佳答案

所以我这样做是这样,但我不知道它是否适合您或者这是否是一个好方法,但是它可以确保注释中所述的.after的安全,这有一个好处,就是您刚刚调用了do_stuff函数需要的时候。

import tkinter as tk
import time
import threading

def get_data():
time.sleep(3)
print('sleeped 3')
_check.set(1)

def do_stuff():
try:
root.configure(bg='#'+str(_var.get()))
_var.set(_var.get()+101010)
except:
_var.set(101010)

root = tk.Tk()
_check = tk.IntVar(value=0)
_var = tk.IntVar(value=101010)


def callback(event=None, *args):
t1 = threading.Thread(target=get_data)
t1.start()

do_stuff()

_check.trace_add('write', callback) #kepp track of that variable and trigger callback if changed
callback() # start the loop



root.mainloop()
一些研究:
[The Tcl]

interpreter is only valid in the thread that created it, and all Tkactivity must happen in this thread, also. That means that themainloop must be invoked in the thread that created theinterpreter. Invoking commands from other threads is possible;_tkinter will queue an event for the interpreter thread, which willthen execute the command and pass back the result.


#l1493 var_invoke
 The current thread is not the interpreter thread.  Marshal

the call to the interpreter thread, then wait for

completion. */

if (!WaitForMainloop(self))

return NULL;
is-it-safe-to-use-a-intvar-doublevar-in-a-python-thread

When you set a variable, it calls the globalsetvar method on themaster widget associated with the Variable. The _tk.globalsetvarmethod is implemented in C, and internally calls var_invoke, whichinternally calls WaitForMainLoop, which will attempt schedule thecommand for execution in the main thread, as described in the quotefrom the _tkinter source I included above.


wiki.tcl
     Start
|
|<----------------------------------------------------------+
v ^
Do I have No[*] Calculate how Sleep for at |
work to do? -----> long I may sleep -----> most that much --->|
| time |
| Yes |
| |
v |
Do one callback |
| |
+-----------------------------------------------------------+

常识
来自 bugtracker:

Tkinter and threads.

If you want to use both tkinter and threads, the safest method is tomake all tkinter calls in the main thread. If worker threads generatedata needed for tkinter calls, use a queue.Queue to send the data tothe main thread. For a clean shutdown, add a method to wait forthreads to stop and have it called when the window close button [X] ispressed.


effbot

Just run all UI code in the main thread, and let the writers write toa Queue object; e.g.


结论
您的操作方式和我的操作方式似乎很像 ideal,但它们似乎根本没有错。这取决于需要什么。

关于python - Tkinter GUI,I/O和线程: When to use queues,什么时候发生?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/63414254/

26 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com