2015-09-08 18 views
11

Questo è nel fork Phoenix di wxPython.blocco thread wxPython

Sto provando a eseguire un paio di thread nell'interesse di non bloccare la GUI.

Due dei miei thread funzionano bene, ma l'altro non sembra mai raggiungere la sua funzione di risultato associata. Posso dire che è in esecuzione, non sembra pubblicare correttamente l'evento.

Ecco la funzione di risultato per i fili conduttori di calcolo:

def on_status_result(self, event): 
    if not self.panel.progress_bar.GetRange(): 
     self.panel.progress_bar.SetRange(event.data.parcel_count) 
    self.panel.progress_bar.SetValue(event.data.current_parcel) 
    self.panel.status_label.SetLabel(event.data.message) 

Ecco come io li sto vincolante:

from wx.lib.pubsub.core import Publisher 
PUB = Publisher() 

Ecco come sto vincolante il metodo:

def post_event(message, data): 
    wx.CallAfter(lambda *a: Publisher().sendMessage(message, data=data)) 

E qui ci sono i fili. Il primo non funziona, ma le altre due fanno:

class PrepareThread(threading.Thread): 
    def __init__(self, notify_window): 
     threading.Thread.__init__(self) 
     self._notify_window = notify_window 
     self._want_abort = False 

    def run(self): 
     while not self._want_abort: 
      for status in prepare_collection(DATABASE, self._previous_id, self._current_id, self._year, self._col_type, 
              self._lock): 
       post_event('prepare.running', status) 
     post_event('prepare.complete', None) 
     return None 

    def abort(self): 
     self._want_abort = True 


class SetupThread(threading.Thread): 
    def __init__(self, notify_window): 
     threading.Thread.__init__(self) 
     self._notify_window = notify_window 
     self._want_abort = False 

    def run(self): 
     while not self._want_abort: 
      do_more_stuff_with_the_database() 
      return None 

    def abort(self): 
     self._want_abort = True 


class LatestCollectionsThread(threading.Thread): 
    def __init__(self, notify_window): 
     threading.Thread.__init__(self) 
     self._notify_window = notify_window 
     self._want_abort = False 

    def run(self): 
     while not self._want_abort: 
      do_stuff_with_my_database() 
      return None 

    def abort(self): 
     self._want_abort = True 

prepare_collection è una funzione che produce Status oggetti che assomiglia a questo:

class Status: 
    def __init__(self, parcel_count, current_parcel, total, message): 
     self.parcel_count = parcel_count 
     self.current_parcel = current_parcel 
     self.total = total 
     self.message = message 

ecco come sto creando/inizio/sottoscrizione il PrepareThread:

MainForm(wx.Form): 
    prepare_thread = PrepareThread(self) 
    prepare_thread.start() 

    self.pub = Publisher() 
    self.pub.subscribe(self.on_status_result, 'prepare.running') 
    self.pub.subscribe(self.on_status_result, 'prepare.complete') 

    def on_status_result(self, event): 
     if not self.panel.progress_bar.GetRange(): 
      self.panel.progress_bar.SetRange(event.data.parcel_count) 
     self.panel.progress_bar.SetValue(event.data.current_parcel) 
     self.panel.status_label.SetLabel(event.data.message) 

ho provato spegnendo prepare_collection con range(10), ma io ancora non mai colpito alla vigilia gestore di NT.

+0

hey morgan mi dispiace ... probabilmente non avrò la possibilità di rivedere questa recensione ... appena super impegnata:/ –

+0

@joran Va tutto bene. –

+0

oh dang ... mi dispiace ... mi proverò provare e aiutare il tuo questo weekend è stata solo una settimana pazzesca –

risposta

4

Questa potrebbe essere una risposta piuttosto complessa ed è un po 'difficile da capire esattamente quale delle i frammenti che hai in ogni parte del tuo codice (ovvero in quali file vivono tutti). Presumo che tu voglia mantenere il metodo pubsub di farlo (che penso sia una buona idea). Se la struttura del progetto vero e proprio è molto complessa, potrebbe essere necessario una gestione più complessa del Publisher oltre che uso qui - fatemi sapere ...


qui va - metto prima lo spoiler - ecco a one file solution per quello che sembri voler - un pannello con un pulsante per dare il via al filo di preparazione, alla barra di stato e al gestore per quando la preparazione è finita. Testato con wxPython Phoenix a 64 bit Python 3 e vecchio stile wxPython su Python 2.7. Sia su Windows - ma posso farlo in una scatola Linux, se necessario.

Riassumendo i più importanti (non boiler-plate) pezzi di quel file

È necessario un solo Publisher oggetto che le discussioni che inviano messaggi da e per il vostro thread principale (MainForm nel tuo esempio credo) sottoscrive. È possibile gestire uno Publisher per ogni thread, ma penso che qui sia necessario solo uno per lo PrepareThread, quindi per ora utilizzerò quel modello.

Nella parte superiore del file, utilizzare

from wx.lib.pubsub import pub 

Ciò consente pubsub gestiscono e un'istanza di un singolo oggetto Publisher.

Nel tuo thread, come si sta facendo, pubblicare messaggi lì - un leggero ammendment al post_event aiutante:

def post_event(message, data): 
    wx.CallAfter(lambda *a: pub.sendMessage(message, data=data)) 

Nel tuo thread principale - iscriversi a quei messaggi. Direi che di solito è più facile avere un gestore per messaggio, invece di inviare due messaggi diversi per lo stesso gestore come eri, così sono andato per

pub.subscribe(self.on_status_result, 'prepare.running') 
pub.subscribe(self.on_status_finished, 'prepare.complete') 

Puoi lasciare on_status_result così com'è e definire un simile on_status_finished. Nel mio esempio, ho avuto questo in seguito abilitando un nuovo pulsante per farti fare del vero lavoro.

N.B. È necessario fare attenzione quando si nomina il carico utile dei tuoi messaggi - pubsub deduce un bel po 'di informazioni su cosa si aspetta lì e mi ha colto in un primo momento.


P.S. Proprio alla fine della preparazione di questa risposta, ho trovato this blog post. Dice qualcosa di simile a quello che ho sopra, quindi non lo riprodurrò, ma usano l'altro metodo di istanziazione dello Publisher() come il tuo esempio originale - il che implica che dovrebbe funzionare anche tu. Potresti preferire la dicitura lì. Similmente, potresti trovare utile la pagina this wxPython wiki.

+0

Questo sembra esattamente quello di cui ho bisogno. Dammi circa un'ora e poi ci provo! –

+0

Funziona! J, sei un bell'uomo. –

+0

buon lavoro .. scusate mi sono occupato ... in wx2.8 chiamate al publisher sendMessage subscribe sono condivisi tra tutte le istanze (infatti Publisher() potrebbe restituire la stessa istanza ...) –

4

il problema è che il sistema di eventi finisce per chiamare la funzione di aggiornamento (gestore di eventi) dai thread stessi, dovresti praticamente non farlo (in pratica finisci con strane condizioni di gara e artefatti) ... fai sempre il callback nel thread principale.

wxPython ha preso in considerazione questo e tutti i metodi chiamati con wx.CallAfter saranno chiamati dal ciclo del programma principale che è sempre in esecuzione nel thread principale. questo combinato con il modulo wx.pubsub permettono di creare il proprio lavoro cornice dell'evento facilmente ... qualcosa di simile

def MyPostEvent(event_name,event_data): 
    #just a helper that triggers the event with wx.CallAfter 
    wx.CallAfter(lambda *a:Publisher().sendMessage(event_name,data=event_data)) 

#then to post an event 

MyPostEvent("some_event.i_made_up",{"payload":True}) 

#then in your main thread subscribe 

def OnEventHandler(evt): 
    print "EVT.data",evt.data 

pub = Publisher() 
pub.subscribe("some_event.i_made_up",OnEventHandler) 
+0

"some_event.i_made_up" sarebbe la stessa cosa che ho passato a MyPostEvent?Inoltre, funziona con Phoenix? –

+0

dovrebbe funzionare su phoenix (potrebbe essere necessario qualche modifica minore ... ma il concetto in generale si applica) –

+0

sì, questo è ciò che passeresti: P (vedi gli esempi su come ricevere ed emettere l'evento) –