2011-08-21 7 views
10

Używam kolejki do wymiany wiadomości między wątkiem tła a aplikacją TK GUI. Obecnie odbywa się to przez wywoływanie metody zapytania co jakiś czas.Tkinter: Poczekaj na element w kolejce

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) 

Problem z tego podejścia jest to, że jeśli UPDATE_TIME jest zbyt duża, aplikacja będzie przetwarzać nowe przedmioty wolniejsze niż to możliwe. Jeśli jest zbyt mały, TK spędza większość czasu na sprawdzaniu kolejki, chociaż w międzyczasie może robić inne rzeczy.

Czy istnieje sposób automatycznego wyzwalania metody read_queue po otrzymaniu nowego elementu w kolejce? (Mogę z pewnością nazwać metodę na Tk gdy wątek tła wypełnia kolejkę, ale obawiam się, że to daje mi pewne problemy współbieżności - dlatego używam kolejki po wszystko.)

+2

Najwyraźniej możesz użyć event_generate z wątku tła, aby wystrzelić wirtualne wydarzenie w twoim GUI. Może to może być wykorzystane jako rodzaj powiadomienia o stanie twojej kolejki. http://groups.google.com/group/comp.lang.python/browse_thread/thread/3476fd30bec12367/853bb6f6dd216960?lnk=gst&q=brunel+%2Bevent_generate#853bb6f6dd216960 –

+0

Wydaje się działać. Możesz dodać ją jako prawdziwą odpowiedź. – Debilski

Odpowiedz

5

PODSUMOWANIE: Nie użyłbym "noob oddy's example code" - jest zasadniczo wadliwym podejściem.

Nie jestem guru Pythona, ale przykładowy kod dostarczony przez "noob oddy" (który wywołuje root.event_generate (...) w wątku tła) wydaje się być "zasadniczo wadliwym podejściem". np. istnieje kilka artykułów w Internecie, które stwierdzają "nigdy nie wywoływać metod/funkcji obiektu Tkinter poza kontekstem" wątku GUI "(który jest zazwyczaj głównym wątkiem). Jego przykład działa "przez większość czasu", ale jeśli zwiększysz wskaźnik generowania zdarzeń, wzrośnie współczynnik awaryjny tego przykładu - jednak konkretne zachowanie zależy od szybkości generowania zdarzeń i charakterystyki wydajności platformy.

Na przykład, używając swojego kodu Pythona 2.7.3, w przypadku zmiany:

 time.sleep(1) 

do:

 time.sleep(0.01) 

następnie skrypt/aplikacja będzie typowo odpoczynku po 'x' liczby iteracje.

Po wielu poszukiwaniach, jeśli "musisz użyć Tkintera", wydaje się, że najbardziej "kuloodporną metodą" pobierania informacji z wątku tła do wątku GUI jest użycie metody widżetów po() do odpytywania obiekt bezpieczny dla wątków (taki jak "Kolejka"). np.

################################################################################ 
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 ################################################################# 
+0

Dzięki za wyjaśnienie. Zapomniałem, że zadałem to pytanie. W moim kodzie ostatecznie zdecydowałem się na to samo rozwiązanie, używając 'after' również (i dodając pewne elementy GUI, aby precyzyjnie dostroić czas oczekiwania - wydajność była nieco inna w zależności od systemu operacyjnego) i nie mieszać się z wewnętrznym menedżerem zdarzeń Tk . – Debilski

+1

ten kod ma błąd. W 'wątku_proc' robisz' dataQ.put (v) 'bez wcześniejszego definiowania' v'. –

+1

Powinieneś usunąć True, zastąp try/except/Break prostym jeśli, i zastąp Get (Timeout = 0.1) odblokowaniem get: Ten sam wynik, jaśniejszy (czyli bardziej niezawodny) kod. (Twoja aplikacja nie zwolni 100ms co 100ms czekając na kolejkę) –

13

Jedną z opcji może być mtTkinter http://tkinter.unpythonic.net/wiki/mtTkinter

Oto kolejny przykład wykorzystania event_generate z wątku tła:

##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('<<TimeChanged>>', 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>>', timeChanged) 

## Run the thread and the GUI main loop 
th=threading.Thread(target=timeThread) 
th.start() 

root.mainloop() 

Istnieje również wspomnieć o użyciu after_idle w podobny sposób.
tj. root.after_idle (timeChanged)

+1

Używanie 'generate_event' jest koncepcyjnie bardziej atrakcyjne, a dwa przykłady od @noob oddy działają. Używając ich jako podstawy, umieściłem figurkę matplotlib w celu utworzenia wykresu w czasie rzeczywistym, pobierającego dane przez sieć. To działało poprawnie w Linuksie, ale nie w systemie Windows (XP, 7,8.1 wszystkie zachowują się w podobny sposób). Problem wydaje się być związany z serią event_generate wywołań, gdy program się uruchamia. Można go uniknąć, czekając na wszystkie już zgromadzone dane do przybycia a następnie generowanie pojedynczego zdarzenia.Ale komunikaty o błędach doprowadziły mnie do przekonania, że ​​'event_generate' ** nie jest bezpieczne dla wątków w systemie Windows ** – NameOfTheRose

+0

** mtTkinter ** rozwiązuje problemy z systemem Windows (udało się przetrwać wybuch 80 000 zdarzeń). To, pośrednio, potwierdza, że ​​jest to problem z gwintowaniem. ** mtTkinter **, za sceną, używa metody 'after', więc nie widzę sensu w jej używaniu. – NameOfTheRose

+0

W międzyczasie dowiedziałem się, że Tkinter jest skompilowany bez obsługi wątków w mojej instalacji Pythona Windows, podczas gdy instalacja Linuksa jest kompilowana z obsługą wątków (przynajmniej to jest to, co mtTkinter raportuje używając 'root.globalgetvar ('tcl_platform (threaded) ') '). To prawdopodobnie jest przyczyną różnicy w zachowaniu. – NameOfTheRose

2

Ankietowanie można wyeliminować z rozwiązania Ken Mumme za pomocą narzędzia os.pipe w celu synchronizacji między dwoma wątkami.

tkinter ma metodę createFilehandler, którą można wykorzystać do dodania deskryptora pliku do pętli wyboru tk. Następnie możesz zasygnalizować, że coś jest gotowe w kolejce, zapisując bajt w rurze.

Rozwiązanie wygląda następująco:

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() 

Nie jestem ekspertem Pythona; przepraszam, jeśli zawiedli mi jakieś konwencje kodowania. Jestem świetny z fajkami :)

+2

FYI nie ma obsługi 'createfilehandler()' w systemie Windows, a kolejka odpytana jest najlepsza. – shuckc