gpt4 book ai didi

python - 为什么这个Tkinter程序卡住了?

转载 作者:行者123 更新时间:2023-12-01 00:25:51 25 4
gpt4 key购买 nike

我的GUI冻结有问题,我不知道为什么。 run方法未释放锁定。

演示程序

import time
import threading
import Tkinter as tk
import ttk

LOCK = threading.Lock()

class Video(threading.Thread):
def __init__(self):
super(Video, self).__init__()
self.daemon = True
self.frame = tk.DoubleVar(root, value=0)
self.frames = 1000

def run(self):
while True:
with LOCK:
position = self.frame.get()

if position < self.frames:
position += 1
else:
position = 0

self.frame.set(position)

time.sleep(0.01)

root = tk.Tk()
video = Video()
root.minsize(500, 50)

def cb_scale(_):
with LOCK:
print('HELLO')

scale = ttk.Scale(
root, from_=video.frame.get(), to=video.frames, variable=video.frame,
command=cb_scale)

scale.grid(row=0, column=0, sticky=tk.EW)
root.columnconfigure(0, weight=1)

if __name__ == '__main__':
video.start()
root.mainloop()


问题

垃圾邮件-单击进度栏将冻结该程序。

尝试调试


我通过在导入语句中添加 mttkinter来使用 import mttkinter,问题仍然存在。问题是锁未释放。
我插入了打印语句,以找出程序在哪里冻结。


带有打印语句的程序:

from __future__ import print_function

import time
import threading
import Tkinter as tk
import ttk

def whichthread(say=''):
t = threading.current_thread()
print('{}{}'.format(say, t))

LOCK = threading.Lock()

class Video(threading.Thread):
def __init__(self):
super(Video, self).__init__()
self.daemon = True
self.frame = tk.DoubleVar(root, value=0)
self.frames = 1000

def run(self):
while True:
whichthread('run tries to acquire lock in thread: ')
with LOCK:
whichthread('run acquired lock in thread: ')

position = self.frame.get()

if position < self.frames:
position += 1
else:
position = 0

self.frame.set(position)
whichthread('run released lock in thread: ')

time.sleep(0.01)

root = tk.Tk()
video = Video()
root.minsize(500, 50)

def cb_scale(_):
whichthread('cb_scale tries to acquire lock in thread: ')
with LOCK:
whichthread('cb_scale acquired lock in thread: ')
print('HELLO')
whichthread('cb_scale released lock in thread: ')

scale = ttk.Scale(
root, from_=video.frame.get(), to=video.frames, variable=video.frame,
command=cb_scale)

scale.grid(row=0, column=0, sticky=tk.EW)
root.columnconfigure(0, weight=1)

if __name__ == '__main__':
video.start()
root.mainloop()


在程序冻结之前,这将产生以下输出:

...
run tries to acquire lock in thread: <Video(Thread-1, started daemon 140308329449216)>
run acquired lock in thread: <Video(Thread-1, started daemon 140308329449216)>
cb_scale tries to acquire lock in thread: <_MainThread(MainThread, started 140308415592256)>


这表明出于某些原因, run方法不会释放该锁。


我试图注释掉行以缩小问题范围。


删除两个 with LOCK语句中的任何一个都可以解决此问题。不幸的是,在我的真实程序中, runcb_scale函数做了一些有意义的事情,需要锁定。

注释掉对 getsetrun中的调用都可以解决此问题。

...这就是我被困住的地方! :)

编辑

多亏了 Mike - SMT,我得以进一步找到问题所在。

使用

class DummyDoubleVar(object):
def get(self):
return 500

def set(self, _):
pass




self.frame = DummyDoubleVar()


Video.__init__中的防止程序冻结。

(请记住,即使使用 mttkinter,原始程序也可以可靠地冻结。我很困惑这里发生的事情!)

最佳答案

在这篇文章中,我将展示问题的解决方案以及导致我发现它的原因。它涉及遍历CPython _tkinter.c代码,因此,如果您不愿意这样做,则可以跳到下面的TL; DR部分。现在,让我们潜入兔子洞。

导致了

仅在手动移动滑杆时才会出现此问题。然后MainThreadVideo线程在LOCK上彼此处于死锁状态,我将其称为用户锁。现在,run方法在获取用户锁之后再也不会释放用户锁,这意味着它正在挂起,因为它正在等待另一个锁或某些无法完成的操作。现在,查看您的详细示例的日志输出,可以清楚地看到该程序未始终挂起:需要进行几次尝试。

通过将更多的打印品添加到run方法,您可能会发现问题并非始终由getset引起。引起问题时,get可能已经完成,或者没有完成。这意味着问题不是由getset引起的,而是由某些更通用的机制引起的。

Variable.set和Variable.get

在本节中,尽管该问题在Python 3.6中也存在,但我只考虑了Python 2.7代码。从CPython 2.7的Variable文件中的Tkinter.py类:

def set(self, value):
"""Set the variable to VALUE."""
return self._tk.globalsetvar(self._name, value)
def get(self):
"""Return value of variable."""
return self._tk.globalgetvar(self._name)


self._tk属性是Tkinter的C代码中定义的Tk对象,对于 globalgetvar的代码,我们必须跳回到 _tkinter.c

static PyObject *
Tkapp_GlobalGetVar(PyObject *self, PyObject *args)
{
return var_invoke(GetVar, self, args, TCL_LEAVE_ERR_MSG | TCL_GLOBAL_ONLY);
}


跳转到 var_invoke

static PyObject*
var_invoke(EventFunc func, PyObject *selfptr, PyObject *args, int flags)
{
#ifdef WITH_THREAD
// Between these brackets, Tkinter marshalls the call to the mainloop
#endif
return func(selfptr, args, flags);
}


只是为了确保:我在线程支持下编译了Python,问题仍然存在。调用被编组到主线程,我在该位置用简单的 printf检查了该线程。现在,这正确完成了吗?函数 var_invoke将等待,直到MainThread恢复并执行了请求的调用。此时MainThread在做什么?好吧,它按照得到事件的顺序执行事件队列。是什么顺序让他们进入的?那取决于时间。这就是导致问题的原因:在某些情况下,Tkinter将在 getset之前但在保持锁定的同时执行对回调的调用。

不管是否导入了 mtTkinter(只要Python被编译为 WITH_THREAD支持),都会将 getset的调用编组到mainloop中,但是那个mainloop可能只是在此时尝试调用回调,它也需要锁...这就是导致死锁和您的问题的原因。因此,基本上 mtTkinter和纯Tkinter提供相同的行为,尽管对于 mtTkinter而言,此行为是在Python代码中引起的,而对于纯Tkinter而言,则是在C代码中引起的。

TL; DR;简而言之

该问题仅由用户锁定引起。 GIL和Tcl解释器锁均不涉及。问题是由于 getset方法将它们的实际调用编组到 MainThread,然后等待此 MainThread完成调用,而 MainThread尝试按顺序执行事件而引起的并先执行回调。

这是预期的行为吗?也许我不确定。我肯定可以看到,对于 ENTER_TCL文件中的所有 LEAVE_TCL_tkinter.c宏,可能比当前的宏有更好的解决方案。但是,到目前为止,除了使用 Tk.after(0, Variable.set)之外,我还没有真正的解决此问题(错误?功能?)的方法,因此,在 Video线程中 MainThread线程不持有锁可能需要它。我的建议是从持有锁的代码中删除 DoubleVar.getset调用。毕竟,如果您的程序执行了有意义的操作,则在设置 DoubleVar时可能不需要保持锁定。或者,如果这不是一个选择,则您将不得不找到其他同步值的方法,例如 DoubleVar的子类。最适合您的需求取决于您的实际应用。

关于python - 为什么这个Tkinter程序卡住了?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/58590281/

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