Python 为什么这个tkinter程序冻结了?
我的GUI冻结有问题,我不知道为什么。Python 为什么这个tkinter程序冻结了?,python,multithreading,tkinter,Python,Multithreading,Tkinter,我的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
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()
这将在程序冻结之前产生以下输出:
...
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)>
及
在视频中。\uuuu init\uuuu
防止程序冻结
(请记住,即使使用,原始程序也会可靠地冻结。我不知道这里发生了什么!)我不知道为什么在单击滑块时程序会100%锁定,但我怀疑这是由于
tk.DoubleVar()
导致的,因为这是主线程的一部分
考虑改用after()
请参阅下面的示例,如果您有任何问题,请告诉我
import tkinter as tk
import tkinter.ttk as ttk
class Video(tk.Tk):
def __init__(self):
super().__init__()
self.minsize(500, 50)
self.daemon = True
self.frames = 1000
self.columnconfigure(0, weight=1)
self.vid_var = tk.DoubleVar(self, value=0)
scale = ttk.Scale(self, from_=self.vid_var.get(), to=self.frames, variable=self.vid_var, command=self.cb_scale)
scale.grid(row=0, column=0, sticky='ew')
self.run()
def cb_scale(self, var):
print('HELLO', var)
def run(self):
position = self.vid_var.get()
if position < self.frames:
position += 1
self.after(10, self.run)
else:
position = 0
self.vid_var.set(position)
if __name__ == '__main__':
Video().mainloop()
将tkinter作为tk导入
将tkinter.ttk导入为ttk
课堂视频(tk.tk):
定义初始化(自):
super()。\uuuu init\uuuuu()
自我调整(500,50)
self.daemon=True
self.frames=1000
self.columnconfigure(0,权重=1)
self.vid_var=tk.DoubleVar(self,value=0)
scale=ttk.scale(self,from=self.vid\u var.get(),to=self.frames,variable=self.vid\u var,command=self.cb\u scale)
scale.grid(行=0,列=0,粘性=ew')
self.run()
def cb_刻度(自身、变量):
打印('HELLO',var)
def运行(自):
position=self.vid_var.get()
如果位置
在这篇文章中,我将展示这个问题的解决方案,以及是什么促使我发现它的。它涉及到检查CPython\u tkinter.c
代码,因此,如果您不打算这样做,您可以直接跳到TL;DR下面的部分。现在,让我们潜入兔子洞
提前期
仅当手动移动滑杆时,才会出现此问题。然后,主线程
和视频
-线程在锁
上彼此处于死锁状态,我称之为用户锁。现在,run
方法在is获得用户锁后不会释放它,这意味着它正在挂起,因为它正在等待另一个锁或某个无法完成的操作完成。现在,看看您的详细示例的日志输出,很明显程序并没有一致地挂起:它需要几次尝试
通过向run
方法添加更多打印,您可能会发现问题不是由get
或set
引起的。造成问题时,get
可能已经完成,也可能尚未完成。这意味着问题不是由get
或set
引起的,而是由某种更通用的机制引起的
Variable.set和Variable.get
在本节中,我只考虑了Python2.7代码,尽管问题也存在于Python3.6中。从CPython 2.7的Tkinter.py
文件中的-class:
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.\u tk
属性是Tkinter的C-code中定义的tk对象,对于globalgetvar
的代码,我们必须跳回:
跳转到:
只是为了确保:我使用线程支持编译了Python,但问题仍然存在。调用被封送到主线程,我在该位置用一个简单的printf
检查了主线程。现在,这样做正确吗?函数var\u invoke
将等待主线程恢复并执行请求的调用。此时主线程在做什么?嗯,它正在执行它的事件队列,按照它得到它们的顺序。它让他们进入了什么序列?这取决于时间。这就是问题的原因:在某些情况下,Tkinter会在get
或set
之前执行对回调的调用,但在保持锁的同时执行
无论是否导入mtTkinter
(只要Python是使用线程支持编译的),对get
和set
的调用都会被编组到mainloop,但mainloop此时可能正试图调用回调,而回调也需要锁。。。这就是导致死锁和您的问题的原因。因此基本上,mtTkinter
和plain Tkinter提供了相同的行为,尽管对于mtTkinter
来说,这种行为是在Python代码中产生的,而对于plain Tkinter来说,它发生在C代码中
TL;博士简而言之
该问题仅由用户锁引起。既不涉及GIL也不涉及Tcl解释器锁。问题是由于get
和set
方法将其实际调用编组到main线程
,然后等待该main线程
完成调用,而main线程
尝试按顺序执行事件并首先执行回调
这是故意的行为吗?也许,我不确定。我可以肯定地看到,在\tkinter.c
文件中有所有的ENTER\TCL
和LEAVE\TCL
宏,可能会有比当前更好的解决方案。不过,目前我还看不到解决这个问题(bug?功能?)的真正方法,除了使用Tk.after(0,Variable.set)
,这样Video
-线程就不会持有锁,而主线程可能需要它。我的建议是从持有锁的代码中删除DoubleVar.get
和set
调用。毕竟,如果您的程序做了一些有意义的事情,那么在设置DoubleVar
时可能不需要保持锁。或者,如果这不是一个选项,那么您必须找到一些其他方法来同步该值,比如DoubleVar
的子类。适合你的需要
self.frame = DummyDoubleVar()
import tkinter as tk
import tkinter.ttk as ttk
class Video(tk.Tk):
def __init__(self):
super().__init__()
self.minsize(500, 50)
self.daemon = True
self.frames = 1000
self.columnconfigure(0, weight=1)
self.vid_var = tk.DoubleVar(self, value=0)
scale = ttk.Scale(self, from_=self.vid_var.get(), to=self.frames, variable=self.vid_var, command=self.cb_scale)
scale.grid(row=0, column=0, sticky='ew')
self.run()
def cb_scale(self, var):
print('HELLO', var)
def run(self):
position = self.vid_var.get()
if position < self.frames:
position += 1
self.after(10, self.run)
else:
position = 0
self.vid_var.set(position)
if __name__ == '__main__':
Video().mainloop()
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)
static PyObject *
Tkapp_GlobalGetVar(PyObject *self, PyObject *args)
{
return var_invoke(GetVar, self, args, TCL_LEAVE_ERR_MSG | TCL_GLOBAL_ONLY);
}
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);
}