我似乎通过使用一些多线程在Linux上破坏tkinter
。就我所见,我正在设法触发一个不是主GUI线程的线程的垃圾回收。这会导致__del__
在tk.StringVar
实例上运行,该实例会尝试从错误的线程调用tcl
堆栈,从而导致linux上的混乱。Tkinter对象被垃圾从错误线程中收集
下面的代码是我已经能够想出的最小例子。请注意,我没有使用matplotlib
做任何实际工作,但我无法触发问题。 Widget
上的__del__
方法验证Widget
实例正在从另一个线程中删除。典型的输出是:
Running off thread on 140653207140096
Being deleted... <__main__.Widget object .!widget2>118576
Thread is 140653207140096
... (omitted stack from from `matplotlib`
File "/nfs/see-fs-02_users/matmdpd/anaconda3/lib/python3.6/site-packages/matplotlib/text.py", line 218, in __init__
elif is_string_like(fontproperties):
File "/nfs/see-fs-02_users/matmdpd/anaconda3/lib/python3.6/site-packages/matplotlib/cbook.py", line 693, in is_string_like
obj + ''
File "tk_threading.py", line 27, in __del__
traceback.print_stack()
...
Exception ignored in: <bound method Variable.__del__ of <tkinter.StringVar object at 0x7fec60a02ac8>>
Traceback (most recent call last):
File "/nfs/see-fs-02_users/matmdpd/anaconda3/lib/python3.6/tkinter/__init__.py", line 335, in __del__
if self._tk.getboolean(self._tk.call("info", "exists", self._name)):
_tkinter.TclError: out of stack space (infinite loop?)
通过修改tkinter
库代码,我可以验证__del__
正在从相同的位置被称为Widget.__del__
。
我的结论是否正确?我怎样才能阻止这种情况发生?
我真的,真的想从一个单独的线程中调用matplotlib
代码,因为我需要产生一些复杂的情节这是缓慢的渲染,所以使他们脱线,生成图像,然后显示图像在tk.Canvas
小部件看起来像一个优雅的解决方案。
小例子:
import tkinter as tk
import traceback
import threading
import matplotlib
matplotlib.use('Agg')
import matplotlib.figure as figure
from matplotlib.backends.backend_agg import FigureCanvasAgg as FigureCanvas
class Widget(tk.Frame):
def __init__(self, parent):
super().__init__(parent)
self.var = tk.StringVar()
#tk.Entry(self, textvariable=self.var).grid()
self._thing = tk.Frame(self)
def task():
print("Running off thread on", threading.get_ident())
fig = figure.Figure(figsize=(5,5))
FigureCanvas(fig)
fig.add_subplot(1,1,1)
print("All done off thread...")
#import gc
#gc.collect()
threading.Thread(target=task).start()
def __del__(self):
print("Being deleted...", self.__repr__(), id(self))
print("Thread is", threading.get_ident())
traceback.print_stack()
root = tk.Tk()
frame = Widget(root)
frame.grid(row=1, column=0)
def click():
global frame
frame.destroy()
frame = Widget(root)
frame.grid(row=1, column=0)
tk.Button(root, text="Click me", command=click).grid(row=0, column=0)
root.mainloop()
注意,在这个例子中,我不需要tk.Entry
部件。 但是如果我注释掉self._thing = tk.Frame(self)
那么我不能重新创建问题!如果我取消然后gc
行,我不明白这一点...
,然后又问题消失(这与我的结论符合...)
更新:这似乎工作同在Windows上的方式。在Windows上的tkinter
似乎更容忍被称为“错误”的线程,所以我没有得到_tkinter.TclError
异常。但是我可以看到在非主线程上调用了析构函数。
恐怕我已经知道这一点。问题是'__del__'是垃圾收集系统调用的一种特殊方法。在我给出的最小例子中,我不会从错误的线程中调用任何'Tkinter'代码:而是垃圾收集系统,它似乎超出了我的控制范围。 –