从错误的线程中垃圾收集的 Tkinter 对象

Mat*_*aws 5 tkinter matplotlib python-multithreading python-3.x

我似乎tkinter通过使用一些多线程来破坏linux。据我所知,我设法在不是主 GUI 线程的线程上触发垃圾回收。这导致__del__在一个tk.StringVar实例上运行,该实例试图tcl从错误的线程调用堆栈,从而导致 linux 上的混乱。

下面的代码是我能想出的最小例子。请注意,我没有用 做任何实际工作matplotlib,但否则我无法触发问题。在__del__对方法Widget验证的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?)
Run Code Online (Sandbox Code Playgroud)

通过修改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()
Run Code Online (Sandbox Code Playgroud)

请注意,在示例中,我不需要tk.Entry小部件。 但是,如果我注释掉该行,self._thing = tk.Frame(self)无法重现该问题!我不明白这个...

如果我取消注释 thengc行,那么问题也会消失(这符合我的结论......)

更新:这在 Windows 上的工作方式似乎相同。 tkinter在 Windows 上似乎更容忍在“错误”线程上被调用,所以我没有得到_tkinter.TclError异常。但是我可以看到在__del__非主线程上调用了析构函数。

Jus*_*ues -1

Tkinter 不是线程安全的。在线程中调用 Tkinter 对象可能会导致诸如“ Widget 上的del方法验证 Widget 实例正在从其他线程中删除”之类的情况。

您可以使用锁定和队列来正确完成它。

检查此示例: Tkinter:如何使用线程来防止主事件循环“冻结”

和这个示例(您可以找到许多其他示例): Mutli-threading python with Tkinter

希望这能让您朝着正确的方向前进。