Tkinter: Esperar el artículo en la cola

Estoy usando una cola para intercambiar mensajes entre un subproceso en segundo plano y una aplicación Tk GUI. Actualmente, esto se hace llamando a un método de consulta de vez en cuando.

def read_queue(self): try: self.process(self.queue.get(False)) # non-blocking except Queue.Empty: pass finally: self.after(UPDATE_TIME, self.read_queue) 

El problema con este enfoque es que si UPDATE_TIME es demasiado grande, la aplicación procesará los nuevos elementos más lentamente de lo posible. Si es demasiado pequeño, Tk pasa la mayor parte del tiempo revisando la cola aunque podría hacer otras cosas mientras tanto.

¿Hay una manera de activar automáticamente el método read_queue cada vez que un nuevo elemento llega a la cola? (Ciertamente, podría llamar a un método en Tk cuando el hilo de fondo llena la cola, pero me temo que esto me da algunos problemas de concurrencia, por eso estoy usando colas después de todo).

RESUMEN: No usaría el ” código de ejemplo de noob oddy “: es un enfoque fundamentalmente defectuoso.

No soy un gurú de Python, pero el código de ejemplo proporcionado por ” noob oddy ” (que llama a root.event_generate (…) dentro del hilo de fondo) parece ser un “enfoque fundamentalmente defectuoso”. es decir, hay varios artículos en Internet que dicen “no invocar nunca funciones / métodos de objetos Tkinter fuera del contexto del ‘subproceso de la GUI'” (que normalmente es el subproceso principal). Su ejemplo funciona “la mayor parte del tiempo”, pero si aumenta la tasa de generación de eventos, la “tasa de choque” del ejemplo boostá; sin embargo, el comportamiento específico depende de la tasa de generación de eventos y las características de rendimiento de la plataforma.

Por ejemplo, usando su código con Python 2.7.3, si cambia:

  time.sleep(1) 

a:

  time.sleep(0.01) 

luego, el script / aplicación normalmente se bloqueará después del número de iteraciones ‘x’.

Después de mucha búsqueda, si “debe usar Tkinter”, parece que el método más “a prueba de balas” para obtener información de un subproceso en segundo plano al subproceso de la GUI es usar el método del widget ‘after ()’ para sondear un objeto seguro para subprocesos (como ‘Queue’). p.ej,

 ################################################################################ import threading import time import Queue import Tkinter as Tk import Tkconstants as TkConst from ScrolledText import ScrolledText from tkFont import Font global top global dataQ global scrText def thread_proc(): x = -1 dataQ.put(x) x = 0 for i in xrange(5): for j in xrange(20): dataQ.put(x) time.sleep(0.1) x += 1 time.sleep(0.5) dataQ.put(x) def on_after_elapsed(): while True: try: v = dataQ.get(timeout=0.1) except: break scrText.insert(TkConst.END, "value=%d\n" % v) scrText.see(TkConst.END) scrText.update() top.after(100, on_after_elapsed) top = Tk.Tk() dataQ = Queue.Queue(maxsize=0) f = Font(family='Courier New', size=12) scrText = ScrolledText(master=top, height=20, width=120, font=f) scrText.pack(fill=TkConst.BOTH, side=TkConst.LEFT, padx=15, pady=15, expand=True) th = threading.Thread(target=thread_proc) th.start() top.after(100, on_after_elapsed) top.mainloop() th.join() ## end of file ################################################################# 

Una opción podría ser mtTkinter http://tkinter.unpythonic.net/wiki/mtTkinter

Aquí hay otro ejemplo del uso de event_generate desde un hilo de fondo:

 ##The only secure way I found to make Tkinter mix with threads is to never ##issue commands altering the graphical state of the application in another ##thread than the one where the mainloop was started. Not doing that often ##leads to random behaviour such as the one you have here. Fortunately, one ##of the commands that seems to work in secondary threads is event_generate, ##giving you a means to communicate between threads. If you have to pass ##information from one thread to another, you can use a Queue. ## ##This obviously complicates things a bit, but it may work far better. ##Please note that the 'when' option *must* be specified in the call to ##event_generate and *must not* be 'now'. If it's not specified or if it's ##'now', Tkinter may directly execute the binding in the secondary thread's ##context. (Eric Brunel) import threading import time import Queue from Tkinter import * ## Create main window root = Tk() ## Communication queue commQueue = Queue.Queue() ## Function run in thread def timeThread(): curTime = 0 while 1: ## Each time the time increases, put the new value in the queue... commQueue.put(curTime) ## ... and generate a custom event on the main window try: root.event_generate('<>', when='tail') ## If it failed, the window has been destoyed: over except TclError: break ## Next time.sleep(1) curTime += 1 ## In the main thread, do usual stuff timeVar = IntVar() Label(root, textvariable=timeVar, width=8).pack() ## Use a binding on the custom event to get the new time value ## and change the variable to update the display def timeChanged(event): timeVar.set(commQueue.get()) root.bind('<>', timeChanged) ## Run the thread and the GUI main loop th=threading.Thread(target=timeThread) th.start() root.mainloop() 

También se menciona el uso de after_idle de manera similar.
es decir. root.after_idle (timeChanged)

El sondeo se puede eliminar de la solución de Ken Mumme usando os.pipe para sincronizar entre los dos hilos.

tkinter tiene un método createFilehandler que se puede usar para agregar un descriptor de archivo en el ciclo de selección de tk. Luego puede indicar que hay algo listo en la cola escribiendo un byte en la tubería.

La solución se ve así:

 import Queue import os uiThreadQueue = Queue.Queue() ; pipe_read, pipe_write = os.pipe() ; # call one function from the queue. Triggered by the # pipe becoming readable through root.tk.createfilehandler(). def serviceQueue(file, mask): os.read(pipe_read, 1) func = uiThreadQueue.get() ; func() # enqueue a function to be run in the tkinter UI thread. # best used as inUIThread(lambda: self.callSomeFunction(arg1,arg2,arg3)) def inUIThread(f): uiThreadQueue.put(f) os.write(pipe_write, "x") ... set up your widgets, start your threads, etc..... root.tk.createfilehandler(pipe_read, tkinter.READABLE, serviceQueue) root.mainloop() 

No soy un experto en python; disculpas si me he equivocado con cualquier convención de encoding. Aunque soy genial con las pipas 🙂