2009-07-29 8 views
5

Ecco il banco di prova ...Tkinter blocca Python quando un'icona è caricato e tk.mainloop è in un thread

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) 

Questo codice funziona. Rimuovi il commento dalla linea t.iconbitmap e blocca. Riorganizzalo come più ti piace; si bloccherà.

Come si impedisce a tk.mainloop di bloccare GIL quando è presente un'icona?

Il target è win32 e Python 2.6.2.

risposta

16

Credo che non si dovrebbe eseguire il ciclo principale su un thread diverso. AFAIK, il ciclo principale dovrebbe essere eseguito sullo stesso thread che ha creato il widget.

I toolkit della GUI che conosco (Tkinter, .NET Windows Form) sono così: È possibile manipolare la GUI da un solo thread.

Su Linux, il codice solleva un'eccezione:

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

una delle seguenti funzionerà (nessun thread aggiuntivi):

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

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

    t.mainloop() 

Con filettatura in più:

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) 

Se devi comunicare con tkinter dall'esterno del thread di tkinter, ti suggerisco di impostare un timer che controlli una coda per il lavoro.

Ecco un esempio:

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

di qualità, avete colpito il chiodo sulla testa, funziona ... ma ho sofferto di non fornire informazioni sufficienti. Il mio ragionamento è che voglio essere in grado di fare cose a tkinter dove il ciclo while è ... Essendo un po 'nuovo a SO, dovrei accettare la tua risposta e fare un'altra domanda più prolissa? – burito

+2

Ciao, ho aggiornato la mia risposta con un suggerimento e un esempio di codice per ottenere ciò. Il ciclo while ora chiama alcuni metodi sul thread di tkinter, usando code di richiesta/risposta. – codeape

+2

BTW, per codice di produzione suggerisco di incapsulare la finestra, il thread e le code di Tkinter in una classe. Questo per evitare le globali che ora abbiamo: request_queue, response_queue e t. Hai anche bisogno di una gestione degli errori attorno a callable (* args, ** kwargs). – codeape

Problemi correlati