当前位置: 首页 > 知识库问答 >
问题:

Tkinter对象正在从错误的线程进行垃圾收集

东门晨
2023-03-14

在linux上,我似乎通过使用一些多线程来打破tkinter。就我所见,我正在设法在一个不是主GUI线程的线程上触发垃圾收集。这导致在tk上运行\uu del\uu。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__相同的地方调用的。

我的结论正确吗?我怎样才能阻止这种事情发生??

我真的,真的想从一个单独的线程调用matplotlibcode,因为我需要生成一些复杂的绘图,这些绘图的渲染速度很慢,因此需要将它们脱离线程,生成一个图像,然后在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。条目小部件。但是,如果我注释掉行self_事物=tk。Frame(self)然后我无法重新创建问题!我不明白这个。。。

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

更新:这在视窗上似乎也是如此。视窗上的tkinter似乎更能容忍被错误的线程调用,所以我没有得到_tkinter。TclErrorhtml" target="_blank">异常。但是我可以看到__del__析构函数在非主线程上被调用。

共有2个答案

燕宏胜
2023-03-14

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

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

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

这个例子(你可以找到很多其他的例子):使用Tkinter的Mutli-thread python

希望这能帮你找到正确的方向。

华恩
2023-03-14

我也有同样的问题

找到问题的原因是一场噩梦。我验证了没有从任何线程调用tkinter对象。我创建了一个基于队列的机制来处理线程中的tkinter对象。网上有很多关于如何做到这一点的例子,或者。。。搜索模块“mttkinter”(Tkinter的线程安全包装器)

为了强制垃圾收集,我在应用程序的每个顶级窗口的退出函数中使用了“gc”方法。

#garbage collector
import gc

...

gc.collect()

但出于某种原因,关闭顶层窗口继续重现问题。无论如何正是在前面提到的“mttkinter”模块中使用了一些打印,我检测到,尽管小部件是在主线程中创建的,但当垃圾收集器在另一个线程中触发时,它们可能会被垃圾收集。垃圾收集器似乎收集所有垃圾,而不区分其来源(主线程还是其他线程?)。如果我错了,请有人纠正我。

我的解决方案是也使用队列显式地调用垃圾收集器。

PutInQueue(gc.collect)

其中“PutInQueue”属于我创建的一个模块,用于处理tkinter对象和其他类型的线程安全对象。

希望这份报告能对某些人很有用,或者,在这种情况下,揭露垃圾收集器中任何最终的错误。

 类似资料:
  • 问题内容: 在Java中,我做了很多类似的事情而没有考虑太多: 但是,最近我不确定这样做是否安全。毕竟,在对象创建之后就没有对它的引用了(嗯,有引用,但是这个值吗?),所以看起来存在垃圾回收器可能会在执行过程中删除该对象的危险的东西。因此,该方法应如下所示: 现在,我很确定第一个版本可以使用,并且我从未遇到任何问题,但是我想知道在所有情况下这样做是否安全(不仅在特定的JVM中,而且最好根据语言规范

  • 有人能给我解释一下原因吗?

  • 在下面的代码中,假设调用了。最初引用的对象在哪一点/哪一行符合垃圾收集的条件? 如果或有一个公共、受保护、默认或静态的访问修饰符,它会影响对象在什么点上有资格进行垃圾收集吗?如果是,它会受到什么影响? 我的第一个想法是,当测试对象有资格进行垃圾收集时,该对象有资格进行垃圾收集

  • 问题内容: 我有两节课 假设我在代码中使用对象B [say ],并在最终使用它后将其设置为。我知道B的对象现在可用于垃圾回收了。 我知道在将b设置为null之后,它将 立即有资格 进行垃圾回收吗?但是类型A的对象呢?将B设置为以后,是否可以 立即 将其用于垃圾回收?还是 在B被垃圾回收之后 才有资格 进行 垃圾回收 ? 从理论上讲,在对B进行垃圾收集之前,还有参考吗?因此,SUN JVM编译器将在

  • 每个java开发人员都知道,java对象不再使用时将被垃圾收集。我想知道JVM如何识别必须为垃圾收集选择的对象。(例如,如果我有10个对象。如果对10个对象中的2个进行垃圾收集,jvm将如何找到这两个对象)。 JVM使用标记和扫描算法(如果我是对的)。 1)例如我在下面提供字符串对象场景 //现在s1

  • [GC(分配失败)[defnew:10931K->472K(12288K),0.0053905秒]10931K->10712K(39616K),0.0054285秒][times:user=0.00 sys=0.00,real=0.01秒] [GC(分配失败)[defnew:10712k->472k(12288k),0.0057686秒]20952k->20952k(39616k),0.00580