Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

tkinter thread communication

I have code that should show communication between tkinter widget (NOTE: not implemented yet) and another thread. As communication between those two I choose python queue. To see what is really happening print is shown in console and it's not what I would expect.

As can be seen in console output after sleep time in generate_text output from process is shown. What I expected is that as generate_text is slower then process I would see a lot more process is called then Item x, but this is not happening.

import tkinter as tk
import threading
import queue
import time

def generate_text(storage):
    count = 0
    while True:
        message = "Item {}".format(count)
        storage.put(message)
        print(message)

        count +=1

        time.sleep(3000/1000)

def process(storage):
    print("process is called")

    try:
        storage.get()
    except queue.Empty:
        print("queue empty")

    # register awake function
    root.after(500, process, message)

# init variables
message = queue.Queue()

root = tk.Tk()

t = threading.Thread(target=generate_text, args=(message,))
t.setDaemon(True)
t.start()

root.after(500, process, message)
root.mainloop()

Output:

Item 0
process is called
process is called
Item 1
process is called
Item 2
process is called
Item 3
process is called...

Desired output:
Item 0
process is called
process is called
process is called
process is called
process is called
process is called
Item 1
like image 461
flebas Avatar asked Dec 13 '22 21:12

flebas


1 Answers

@Himal's answer is correct for the current question however you might want to amend this to use event_generate in the message generating code and have the UI respond to the events when they are raised rather than polling the queue like this. You can use root.event_generate('<<MessageQueued>>') in the generate_text fuction to place a virtual event onto Tk's event queue. This is thread safe where calling window methods directly is not. If you also add a binding to this virtual event on the UI code then the Tk message loop with call the bound function when it receives the virtual event. No more polling.

import tkinter as tk
import threading
import queue
import time

def generate_text(mainwin, storage):
    count = 0
    while True:
        message = "Item {}".format(count)
        storage.put(message)
        print("Queued {0}".format(message))
        count += 1
        mainwin.event_generate('<<MessageGenerated>>')
        time.sleep(3000/1000)

def process(storage, event):
    msg = storage.get()
    print("New message: {0}".format(msg))

def main():
    message_queue = queue.Queue()
    root = tk.Tk()
    root.bind('<<MessageGenerated>>', lambda e: process(message_queue, e))
    t = threading.Thread(target=generate_text, args=(root, message_queue,))
    t.setDaemon(True)
    t.start()
    root.mainloop()

if __name__ == '__main__':
    main()
like image 57
patthoyts Avatar answered Jun 09 '23 06:06

patthoyts