不久前,我发布了一个有关在 GUI 循环内启动线程的问题,其中命令应与 GUI 事件/绘图循环并行运行。这应该是一个测量循环,应根据用户的决定启动和停止。我从 stackoverflow 得到了答案,并且我已将代码实现到我的应用程序中。它似乎有效,但它减慢了 GUI 元素的绘制和响应速度(当然它不应该,因为它在单独的线程中运行)。它滞后太多,以至于 GUI 变得几乎没有响应。尽管单独线程中的测量循环以正常速度运行。我应该在另一个线程中运行 GUI 的重绘吗?缩短的代码发布在下面。尽管我尝试输入了解应用程序如何工作所必需的所有内容,但某些元素可能会丢失。
#function to execute in separate thread - if measuring puts a random number in a queue
def takeMeasurements(run, measure, q, sleep_time):
while run.is_set():
if measure.is_set():
print("Measuring...")
q.put(random())
else:
print("Not measuring...")
sleep(sleep_time)
########pygame code########
pygame.init()
#here goes a lot of repetitive GUI code
#{....}
run=True
MEASURE=False
#event and GUI update loop
while run:
time_delta = clock.tick(60)/1000.0
screen.fill((0,0,0))
for event in pygame.event.get():
if event.type == pygame.QUIT:
run=False
if event.type== pygame_gui.UI_BUTTON_PRESSED:
if event.ui_element == button_set_param:
#if user pushes button starting measurement
if stdb_run=="Standby":
pass
elif stdb_run=="Run":
if MEASURE==False:
#initializing threading, only when MEASURE==False, so it executes only once
(threadRun := Event()).set()
(threadMeasure := Event()).set()
queue = Queue()
#start thread with sleep=1 s
(t := Thread(target=takeMeasurements, args=[threadRun, threadMeasure, queue, 1])).start()
else:
#if measurement run second time or more
threadMeasure.set()
MEASURE=True
if event.type == pygame_gui.UI_BUTTON_PRESSED:
#if User pushes stop button and stops the measurement
if event.ui_element == button_stop:
threadMeasure.clear()
try:
#if the measuremnt was started, and queue object exists, if not -> pass
m=queue.get()
#displaying a random number in GUI element
entry_vbridge.set_text(str(m))
except:
pass
#updating GUI
manager.update(time_delta)
manager.draw_ui(screen)
screen.blit(surf, (750,400))
pygame.display.flip()
#code on closing the app
try:
threadRun.clear()
t.join()
except NameError:
threadRun=None
t=None
pygame.quit()
我愚蠢地认为 takeMeasurements 函数中的 sleep() 命令不仅在线程中起作用,而且在 GUI 更新循环中以某种方式起作用。有 thread.sleep(),但我不能在函数体中使用它,因为线程没有作为参数传递给函数。
这里的主要问题似乎是 Python 中不存在“真正的”多线程——至少如果您使用的是常规 Python 实现(即 CPython),则不会。这意味着在任何时刻都只能有一个 Python 级别的线程在运行,因为任何正在运行的线程都需要获取 GIL(全局解释器锁)。换句话说,Python 级线程与 C 级线程有很大不同,并且只对 IO 密集型任务有意义。如果你有一个非 IO 密集型、计算量大的任务,比如“进行测量”,那么它将阻塞其他任务 - 除非你的第一个任务自动且非常定期地释放 GIL,将控制权交还给 Python 主线程,以便其他任务可以运行。
避免干扰的一种方法是在 Cython 中或作为 Python 扩展模块实现“进行测量”代码,并确保它将释放 GIL(无论是在运行时的大部分时间还是非常定期)。