【发布时间】:2017-11-30 14:20:18
【问题描述】:
我似乎通过使用一些多线程来破坏 Linux 上的tkinter。据我所见,我设法在不是主 GUI 线程的线程上触发垃圾收集。这导致__del__ 在tk.StringVar 实例上运行,该实例试图从错误的线程调用tcl 堆栈,从而导致linux 混乱。
下面的代码是我能想到的最小示例。请注意,我没有对matplotlib 进行任何实际工作,但否则我无法触发问题。 Widget 上的 __del__ 方法验证是否正在从另一个线程中删除 Widget 实例。典型的输出是:
Running off thread on 140653207140096
Being deleted... <__main__.Widget object .!widget2> 140653210118576
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__ 析构函数。
【问题讨论】:
标签: python-3.x matplotlib tkinter python-multithreading