1

I'm trying to set up a tkinter window that I can interact with outside of the main loop, using a queue. When I interpret this in spyder, it works fine. after launch()-ing, my Tk window appears, and I still have console access, allowing me to change_titre('whatever') to change the title of the window.

However, closing the window is an issue. It closes fine, checking mythread shows that the thread indeed stopped. But then calling launch() again does nothing and blocks the interpreter. I am then forced to restart python :(

Is there something that needs cleaning that prevents me from creating a new thread ? From what I'm reading around here, tkinter does not like not being run in main, which is what I'm doing here. But Why is the first instance working then ?

I would like to be able to code a few low-level functions like change_titre below (drawing basic stuff for example), and then allow the user to code his own functions using those. If all fails, is there another way to go ?

import tkinter as tk
from threading import Thread
#import threading
import queue

request_queue = None
master = None
mythread = None

def submit_to_tkinter(callable, *args, **kwargs):
    request_queue.put((callable, args, kwargs))
    return

def threadmain():
    global master
    master = tk.Tk()
    master.title("stuff")
    drawzone = tk.Canvas(master, width=300, height = 300, bg='white')
    drawzone.pack()
    def queueloop():
        try:
            callable, args, kwargs = request_queue.get_nowait()
        except queue.Empty:
            pass
        else:
            callable(*args, **kwargs)
        master.after(500, queueloop)
    queueloop()
    master.mainloop()

def change_titre(text):
    submit_to_tkinter(master.title,text)
    return


def launch():
    global mythread,request_queue
    request_queue = queue.Queue()
    mythread = Thread(target=threadmain, args=())
    mythread.daemon=True
    mythread.start()
imj
  • 223
  • 1
  • 2
  • 6
  • The first instance does not work for me and causes python to segfault straight away. As you say, you shouldn't be running Tkinter in any thread but the main thread. What is the purpose of this code, what is it you want it to do? – ebarr Sep 29 '14 at 12:21
  • this is for teaching purposes : I'm trying to make students create their own functions using low-level functions I'm giving them. – imj Sep 30 '14 at 19:29

1 Answers1

-1

I believe using global variables in this code is just an easy way to access the data from Python REPL (since the code is supposed to be for teaching purposes). But keeping the Tk object in a global variable and access it from different threads is the root of this issue.

I think setting master (global variable) to a new Tk object on each new launch could help. So we could make sure previous Tk object is garbage collected when launch() is finished and It's thread is joined.

Here are the changed functions (comments show which parts are changed).

# import garbage collection module
import gc

def threadmain():
    global master
    master = tk.Tk()
    master.title("stuff")
    drawzone = tk.Canvas(master, width=300, height = 300, bg='white')
    drawzone.pack()
    def queueloop():
        try:
            callable_, args, kwargs = request_queue.get_nowait()
        except queue.Empty:
            pass
        else:
            callable_(*args, **kwargs)
        master.after(500, queueloop)
    queueloop()
    master.mainloop()
    # added these 2 lines to remove previous Tk object when thread is finished
    master = None
    gc.collect()

def launch():
    global mythread,request_queue
    # added these 3 lines to end previous open thread if any
    if mythread and mythread.isAlive():
        submit_to_tkinter(master.destroy)
        mythread.join()
    request_queue = queue.Queue()
    mythread = Thread(target=threadmain, args=())
    # no need for daemon threads
    # mythread.daemon=True
    mythread.start()

Now on each call to launch() it will close previous Tk window and wait for the thread to join, before opening a new Tk in a new thread.

farzad
  • 8,775
  • 6
  • 32
  • 41
  • I adapted your code for python 3 (the print statement), but unfortunatly this did not solve the problem. Second call still freezes. Besides, I thought setting master to a new TK object is what I was doing. Thanks anyway ! – imj Oct 01 '14 at 18:43
  • Calling it threadmain does not make it the main thread, this solution still suffers from the same problems as the OP's solution. See questions such as http://stackoverflow.com/questions/10556479/running-a-tkinter-form-in-a-separate-thread for discussion on why Tkinter does not work when not in the main thread – ebarr Oct 01 '14 at 23:42
  • @imj First time I tested it on Python 2.7.6 (hence the print statement) and after seeing your comment I tested it on Python 3.4 as well. Multiple calls to launch() will open a new window (closes previous window if any) without freezing. I'm testing on x64 Linux machine. – farzad Oct 02 '14 at 01:32
  • @ebarr True, but I rather to keep the answer close to the question so the added parts that are helping to fix the problem are easier to find. "threadmain" is name of the function in the question that needed to be changed. As mentioned in my other comment I posted the updated functions because they are working without freezing (at least on my machine). – farzad Oct 02 '14 at 02:11
  • @farzad Are you running this in spyder, the python IDE, IPython or something else? Running in either the python IDE or IPython causes a segfault on my machine (64-bit Mac OSX 10.8). – ebarr Oct 02 '14 at 07:28
  • @ebarr: No I'm running it in standard Python REPL, on Linux Mint 17 (arch x64). I guess it's Tkinter behaving differently on different platforms. The platform being used in the question is not specified either, but I guess it could be different than the one I tested on. I've had experience with Tkinter and python threads behaving differently on Linux and Windows before (same app, same code worked properly in Linux but froze on Windows). – farzad Oct 02 '14 at 14:54
  • I'm using Spyder and python 3.3.5 x64 on a windows 7 machine. If this stuff is machine dependant then I'm giving up, to many architectures around (students are working with XP/iOS/seven and maybe one unix machine). a second call to launch closes the first window, but doesn't open a new one. – imj Oct 02 '14 at 17:29
  • I heard pygame had a queue that might work for this kind of stuff ? maybe if it's not too hard to deploy at work. Anyone with experience ? – imj Oct 02 '14 at 17:30