tkinter tkMessageBox not working in thread

前端 未结 3 675
遥遥无期
遥遥无期 2021-01-22 08:08

i have tkinter class and some functions in it, (assume all other functions are present to initiate the GUI). what i have done i have started one self.function as a thread from o

相关标签:
3条回答
  • 2021-01-22 08:44

    tkinter is not thread safe -- you can't reliably call any tkinter functions from any thread other than the one in which you initialized tkinter.

    0 讨论(0)
  • 2021-01-22 08:49

    If you want your other thread to block until you get response (e,g: you want to ask a question and wait for the answer) you can use this function:

    def runInGuiThreadAndReturnValue(self, fun, *args, **kwargs):
        def runInGui(fun, ret, args, kwargs):
            ret.append(fun( *args, **kwargs))
        ret = []
        sleeptime = kwargs.pop('sleeptime', 0.5)
        self.after(0, runInGui, fun, ret, args, kwargs)
        while not ret:
            time.sleep(sleeptime)
        return ret[0]
    
    0 讨论(0)
  • 2021-01-22 08:52

    Since I got stuck on the same problem and didn't find a proper, well explained solution, I'd like to share a basic strategy I came out with.

    Note that this is not the only nor the best way to do threading with tkinter, but it's quite straightforward and should preserve your workflow if you designed your code without being aware of tkinter's thread-unsafetiness.

    Why threads?

    First of all, I chose to use threads seeing that blocking actions like os.popen, subprocess.call, time.sleep and the like would "freeze" the GUI until they run (of course this may not be your case since threads are useful by their own for many reasons and sometimes they are just needed).

    This is how my code looked like before using threads:

    from Tkinter import *
    import tkMessageBox
    from time import sleep
    
    # Threadless version.
    # Buttons will freeze the GUI while running (blocking) commands.
    
    def button1():
        sleep(2)
        tkMessageBox.showinfo('title', 'button 1')
    
    def button2():
        sleep(2)
        tkMessageBox.showinfo('title', 'button 2')
    
    root = Tk()
    frame = Frame(root)
    frame.pack()
    
    Frame(root).pack( side = BOTTOM )
    Button(frame, command=button1, text="Button 1").pack( side = LEFT )
    Button(frame, command=button2, text="Button 2").pack( side = LEFT )
    root.mainloop()
    

    Buggy threaded version

    Then I turned the commands called by the buttons into threads. This way, the GUI would not freeze.

    I thought it was ok, but on Windows this code leads the interpreter to crash irreparably due to the tkMessageBoxes called from threads other than the one in which the tkinter's root is running:

    from Tkinter import *
    import tkMessageBox
    from time import sleep
    import threading
    
    # Buggy threads.
    # WARNING: Tkinter commands are run into threads: this is not safe!!!
    
    def button1():
        sleep(2)
        tkMessageBox.showinfo('title', 'button 1')
    
    def button2():
        sleep(2)
        tkMessageBox.showinfo('title', 'button 2')
    
    def start_thread(fun, a=(), k={}):
        threading.Thread(target=fun, args=a, kwargs=k).start()
    
    root = Tk()
    frame = Frame(root)
    frame.pack()
    
    Frame(root).pack( side = BOTTOM )
    Button(frame, command=lambda: start_thread(button1), text="Button 1").pack( side = LEFT)
    Button(frame, command=lambda: start_thread(button2), text="Button 2").pack( side = LEFT )
    root.mainloop()
    

    Thread-safe version

    When I discovered the thread-unsafetiness of tkinter, I wrote a small function tkloop that would run in the main thread each few milliseconds checking requests and executing requested (tkinter) functions on behalf of the threads that wish to run them.

    The two keys here are the widget.after method that "registers a callback function that will be called after a given number of milliseconds" and a Queue to put and get requests.

    This way, a thread can just put the tuple (function, args, kwargs) into the queue instead of calling the function, resulting in a unpainful change of the original code.

    This is the final, thread-safe version:

    from Tkinter import *
    import tkMessageBox
    from time import sleep
    import threading
    from Queue import Queue
    
    # Thread-safe version.
    # Tkinter functions are put into queue and called by tkloop in the main thread.
    
    q = Queue()
    
    def button1():
        sleep(2)
        q.put(( tkMessageBox.showinfo, ('title', 'button 1'), {} ))
    
    def button2():
        sleep(2)
        q.put(( tkMessageBox.showinfo, ('title', 'button 2'), {} ))
    
    def start_thread(fun, a=(), k={}):
        threading.Thread(target=fun, args=a, kwargs=k).start()
    
    def tkloop():
        try:
            while True:
                f, a, k = q.get_nowait()
                f(*a, **k)
        except:
            pass
    
        root.after(100, tkloop)
    
    
    root = Tk()
    frame = Frame(root)
    frame.pack()
    
    Frame(root).pack( side = BOTTOM )
    Button(frame, command=lambda: start_thread(button1), text="Button 1").pack( side = LEFT)
    Button(frame, command=lambda: start_thread(button2), text="Button 2").pack( side = LEFT )
    tkloop() # tkloop is launched here
    root.mainloop()
    

    Edit: two-way communication: if your threads need to get informations from the main (e.g. return values from tkinter functions) you can edit the interface of tkloop adding a queue for the return values. Here's an example based on the code above:

    def button1():
        q1 = Queue()
        sleep(2)
        q.put(( tkMessageBox.askokcancel, ('title', 'question'), {}, q1 ))
        response = 'user said ' + 'OK' if q1.get() else 'CANCEL'
        q.put(( tkMessageBox.showinfo, ('title', response), {}, None ))
    
    # ...
    
    def tkloop():
        try:
            while True:
                f, a, k, qr = q.get_nowait()
                r = f(*a, **k)
                if qr: qr.put(r)
        except:
            pass
    
        root.after(100, tkloop)
    
    0 讨论(0)
提交回复
热议问题