2009-07-29 7 views
5

Oto przypadek testowy ...Tkinter blokuje Python, gdy ikona jest załadowany i tk.mainloop jest w wątku

import Tkinter as tk 
import thread 
from time import sleep 

if __name__ == '__main__': 
    t = tk.Tk() 
    thread.start_new_thread(t.mainloop,()) 
    # t.iconbitmap('icon.ico') 

    b = tk.Button(text='test', command=exit) 
    b.grid(row=0) 

    while 1: 
     sleep(1) 

Ten kod działa. Odkomentuj linię t.iconbitmap i ją zablokuj. Przeorganizuj go w dowolny sposób; to się zablokuje.

Jak zapobiec blokowaniu pliku tk.mainloop na GIL, gdy znajduje się ikona?

Celem jest win32 i Python 2.6.2.

Odpowiedz

16

Uważam, że nie powinieneś wykonywać głównej pętli w innym wątku. AFAIK, pętla główna powinna być wykonana w tym samym wątku, który utworzył widget.

Zestawy narzędzi GUI, które znam (Tkinter, .NET Windows Forms) są następujące: Możesz manipulować GUI tylko z jednego wątku.

W systemie Linux kod podnosi wyjątek:

 
self.tk.mainloop(n) 
RuntimeError: Calling Tcl from different appartment 

Jeden z poniższych będzie działać (bez dodatkowych wątków):

if __name__ == '__main__': 
    t = tk.Tk() 
    t.iconbitmap('icon.ico') 

    b = tk.Button(text='test', command=exit) 
    b.grid(row=0) 

    t.mainloop() 

z dodatkowym gwintem:

def threadmain(): 
    t = tk.Tk() 
    t.iconbitmap('icon.ico') 
    b = tk.Button(text='test', command=exit) 
    b.grid(row=0) 
    t.mainloop() 


if __name__ == '__main__': 
    thread.start_new_thread(threadmain,()) 

    while 1: 
     sleep(1) 

Jeśli potrzebujesz komunikacji z tkinterem spoza wątku tkintera, proponuję skonfigurować zegar, który sprawdza kolejkę do pracy.

Oto przykład:

import Tkinter as tk 
import thread 
from time import sleep 
import Queue 

request_queue = Queue.Queue() 
result_queue = Queue.Queue() 

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

t = None 
def threadmain(): 
    global t 

    def timertick(): 
     try: 
      callable, args, kwargs = request_queue.get_nowait() 
     except Queue.Empty: 
      pass 
     else: 
      print "something in queue" 
      retval = callable(*args, **kwargs) 
      result_queue.put(retval) 

     t.after(500, timertick) 

    t = tk.Tk() 
    t.configure(width=640, height=480) 
    b = tk.Button(text='test', name='button', command=exit) 
    b.place(x=0, y=0) 
    timertick() 
    t.mainloop() 

def foo(): 
    t.title("Hello world") 

def bar(button_text): 
    t.children["button"].configure(text=button_text) 

def get_button_text(): 
    return t.children["button"]["text"] 

if __name__ == '__main__': 
    thread.start_new_thread(threadmain,()) 

    trigger = 0 
    while 1: 
     trigger += 1 

     if trigger == 3: 
      submit_to_tkinter(foo) 

     if trigger == 5: 
      submit_to_tkinter(bar, "changed") 

     if trigger == 7: 
      print submit_to_tkinter(get_button_text) 

     sleep(1) 
+2

Dobrze pan uderzyć sedno, to działa ... ale ja nie cierpiał na dostarczenie wystarczającej ilości informacji. Moje rozumowanie polega na tym, że chcę mieć możliwość robienia rzeczy, które mają miejsce w pętli while ... Będąc trochę nowym dla SO, czy powinienem zaakceptować twoją odpowiedź i zadać inne, bardziej szczegółowe pytanie? – burito

+2

Witam, zaktualizowałem swoją odpowiedź sugestią i przykładem kodu, który to umożliwia. Pętla while wywołuje teraz kilka metod w wątku TKKeera, używając kolejek żądań/odpowiedzi. – codeape

+2

BTW, dla kodu produkcyjnego Proponuję enkapsulować okno Tkinter, wątek i kolejki w klasie. Dzięki temu unikniemy globali, które mamy teraz: request_queue, response_queue i t. Potrzebujesz również obsługi błędów wokół call (* args, ** kwargs). – codeape

Powiązane problemy