title: Tkinter objects being garbage collected from the wrong thread
body: <p>I seem to be breaking <code>tkinter</code> on linux by using some multi-threading.  As far as I can see, I am managing to trigger a garbage collection on a thread which is not the main GUI thread.  This is causing <code>__del__</code> to be run on a <code>tk.StringVar</code> instance, which tries to call the <code>tcl</code> stack from the wrong thread, causing chaos on linux.</p>
  
  <p>The code below is the minimal example I've been able to come up with.  Notice that I'm not doing any real work with <code>matplotlib</code>, but I can't trigger the problem otherwise.  The <code>__del__</code> method on <code>Widget</code> verifies that the <code>Widget</code> instance is being deleted from the other thread. Typical output is:</p>
  
  <pre><code>Running off thread on 140653207140096
  Being deleted... &lt;__main__.Widget object .!widget2&gt; 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: &lt;bound method Variable.__del__ of &lt;tkinter.StringVar object at 0x7fec60a02ac8&gt;&gt;
  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?)
  </code></pre>
  
  <p>By modifying the <code>tkinter</code> library code, I can verify that <code>__del__</code> is being called from the same place as <code>Widget.__del__</code>.</p>
  
  <blockquote>
    <p>Is my conclusion here correct?  How can I stop this happening??</p>
  </blockquote>
  
  <p>I really, really want to call <code>matplotlib</code> code from a separate thread, because I need to produce some complex plots which are slow to render, so making them off-thread, generating an image, and then displaying the image in a <code>tk.Canvas</code> widget seemed like an elegant solution.</p>
  
  <p>Minimal example:</p>
  
  <pre><code>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()
  </code></pre>
  
  <p>Notice that in the example, I don't need the <code>tk.Entry</code> widget.  <em>However</em> if I comment out the line <code>self._thing = tk.Frame(self)</code> then I <em>cannot</em> recreate the problem!  I don't understand this...</p>
  
  <p>If I uncomment then <code>gc</code> lines, then also the problem goes away (which fits with my conclusion...)</p>
  
  <p><em>Update:</em> This seem to work the same way on Windows.  <code>tkinter</code> on Windows seems more tolerant of being called on the "wrong" thread, so I don't get the <code>_tkinter.TclError</code> exception.  But I can see the <code>__del__</code> destructor being called on the non-main thread.</p>