Warning: file_get_contents(/data/phpspider/zhask/data//catemap/2/python/283.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Python 为什么这个tkinter程序冻结了?_Python_Multithreading_Tkinter - Fatal编程技术网

Python 为什么这个tkinter程序冻结了?

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

我的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()
这将在程序冻结之前产生以下输出:

...
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);
}