- html - 出于某种原因,IE8 对我的 Sass 文件中继承的 html5 CSS 不友好?
- JMeter 在响应断言中使用 span 标签的问题
- html - 在 :hover and :active? 上具有不同效果的 CSS 动画
- html - 相对于居中的 html 内容固定的 CSS 重复背景?
我正在使用TKinter来构建GUI(用于与多 channel 分析仪的套接字连接),以按固定间隔(〜15秒)接收和绘制数据(〜15.000.000值)。
在接收数据时,我不希望GUI冻结,因此我正在使用多线程进行连接处理,数据接收和绘图操作。如可重现的代码所示,我通过用threading.Event()
设置了一个事件并依次处理一个线程(initSettings()
和acquireAndPlotData
中的几行代码)来实现这一点。我唯一一次干扰GUI的时候是在绘制到 Canvas 上时,我使用tkinters after()
方法执行此操作。
启动后,只要打开窗口并按预期工作,代码便会在不冻结的情况下运行并接收并绘图。
在阅读有关在tkinter GUI中处理阻塞I/O操作的文章时,我仅发现了一些示例,这些示例以递归方式对队列进行排队和检查(使用Queue
和after()
,
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()
一些研究:
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.
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.
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 |
| |
+-----------------------------------------------------------+
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.
Just run all UI code in the main thread, and let the writers write toa Queue object; e.g.
关于python - Tkinter GUI,I/O和线程: When to use queues,什么时候发生?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/63414254/
我在优化 JOIN 以使用复合索引时遇到问题。我的查询是: SELECT p1.id, p1.category_id, p1.tag_id, i.rating FROM products p1
我有一个简单的 SQL 查询,我正在尝试对其进行优化以删除“使用位置;使用临时;使用文件排序”。 这是表格: CREATE TABLE `special_offers` ( `so_id` int
我有一个具有以下结构的应用程序表 app_id VARCHAR(32) NOT NULL, dormant VARCHAR(6) NOT NULL, user_id INT(10) NOT NULL
此查询的正确索引是什么。 我尝试为此查询提供不同的索引组合,但它仍在使用临时文件、文件排序等。 总表数据 - 7,60,346 产品= '连衣裙' - 总行数 = 122 554 CREATE TAB
为什么额外的是“使用where;使用索引”而不是“使用索引”。 CREATE TABLE `pre_count` ( `count_id`
我有一个包含大量记录的数据库,当我使用以下 SQL 加载页面时,速度非常慢。 SELECT goal.title, max(updates.date_updated) as update_sort F
我想知道 Using index condition 和 Using where 之间的区别;使用索引。我认为这两种方法都使用索引来获取第一个结果记录集,并使用 WHERE 条件进行过滤。 Q1。有什
I am using TypeScript 5.2 version, I have following setup:我使用的是TypeScript 5.2版本,我有以下设置: { "
I am using TypeScript 5.2 version, I have following setup:我使用的是TypeScript 5.2版本,我有以下设置: { "
I am using TypeScript 5.2 version, I have following setup:我使用的是TypeScript 5.2版本,我有以下设置: { "
mysql Ver 14.14 Distrib 5.1.58,用于使用 readline 5.1 的 redhat-linux-gnu (x86_64) 我正在接手一个旧项目。我被要求加快速度。我通过
在过去 10 多年左右的时间里,我一直打开数据库 (mysql) 的连接并保持打开状态,直到应用程序关闭。所有查询都在连接上执行。 现在,当我在 Servicestack 网页上看到示例时,我总是看到
我使用 MySQL 为我的站点构建了一个自定义论坛。列表页面本质上是一个包含以下列的表格:主题、上次更新和# Replies。 数据库表有以下列: id name body date topic_id
在mysql中解释的额外字段中你可以得到: 使用索引 使用where;使用索引 两者有什么区别? 为了更好地解释我的问题,我将使用下表: CREATE TABLE `test` ( `id` bi
我经常看到人们在其Haxe代码中使用关键字using。它似乎在import语句之后。 例如,我发现这是一个代码片段: import haxe.macro.Context; import haxe.ma
这个问题在这里已经有了答案: "reduce" or "apply" using logical functions in Clojure (2 个答案) 关闭 8 年前。 “and”似乎是一个宏,
这个问题在这里已经有了答案: "reduce" or "apply" using logical functions in Clojure (2 个答案) 关闭 8 年前。 “and”似乎是一个宏,
我正在考虑在我的应用程序中使用注册表模式来存储指向某些应用程序窗口和 Pane 的弱指针。应用程序的一般结构如下所示。 该应用程序有一个 MainFrame 顶层窗口,其中有几个子 Pane 。可以有
奇怪的是:。似乎a是b或多或少被定义为id(A)==id(B)。用这种方式制造错误很容易:。有些名字出人意料地出现在Else块中。解决方法很简单,我们应该使用ext==‘.mp3’,但是如果ext表面
我遇到了一个我似乎无法解决的 MySQL 问题。为了能够快速执行用于报告目的的 GROUP BY 查询,我已经将几个表非规范化为以下内容(该表由其他表上的触发器维护,我已经同意了与此): DROP T
我是一名优秀的程序员,十分优秀!