Seite 1 von 1

Gui-Thread und Worker-Thread

Verfasst: Freitag 14. Oktober 2011, 11:30
von Schaf220
Hallo liebe Community,
ich habe mal ein bisschen mit Threads rumgespielt, dabei ist mir etwas aufgefallen.
Als erstes habe ich eine GUI auf der zwei Buttons sind. Einer startet den Thread/s und der andere Schließt die GUI.
Dazu habe ich eine WorkerThread, der etwas berechnet und sein Ergebnis mittels wx.CallAfter an die Gui übermittelt.
Anschließend warete ich mit thread.join() darauf das er beendet wurde, um danach weiter mit dem Ergebnis des Threads zu arbeiten.

Ich bekomme aber nicht das errechnete Ergebnis, weil nicht gewartet wird bis der Thread fertig ist. Was kann ich tun oder muss ich verändern?


Hier das Beispiel:

Code: Alles auswählen

#!/usr/bin/env python
# -*- coding: iso-8859-1 -*-

import wx
import threading

class MyButtons(wx.Dialog):
    def __init__(self, parent, id, title):
        wx.Dialog.__init__(self, parent, id, title, size = (300, 200))

        wx.Button(self, 1, 'Close', (50, 130))
        wx.Button(self, 2, 'Start Thread', (150, 130), (110, -1))
        self.liste = None
        self.threads = []

        self.Bind(wx.EVT_BUTTON, self.OnClose, id = 1)
        self.Bind(wx.EVT_BUTTON, self.OnStartThread, id = 2)

        self.Centre()
        self.ShowModal()
        self.Destroy()
        
        
    def setListe(self, liste):
        self.liste = liste
        print "SetListe wurde aktiviert: ", self.liste

    def OnClose(self, event):
        self.Close(True)

    def OnStartThread(self, event):
        self.liste = None
        newThread = WorkerThread(self)
        self.threads.append(newThread)
        newThread.start()

        
        for thread in self.threads:
            thread.join
            
        print "Nach join: ", self.liste
        

class WorkerThread(threading.Thread):
    """
    WorkerThread
    """
    def __init__(self, window):
        threading.Thread.__init__(self)
        self.window = window      
        self.liste = None

    def run(self):
        self.liste = self.fillList()
        wx.CallAfter(self.window.setListe, self.liste)
        
    def fillList(self):
        for i in range(5):
            yield i
             
            
if __name__ == "__main__":
    app = wx.App(0)
    MyButtons(None, -1, 'ThreadTest')
    app.MainLoop()


Mit freundlichen Grüßen
Schaf220

Re: Gui-Thread und Worker-Thread

Verfasst: Freitag 14. Oktober 2011, 13:50
von Dav1d
Wie es aussieht hängt das mit wx.CallAfter zusammen (wenn man CallAfter weglässt, funktioniert es), das würde in so fern Sinn machen, da CallAfter die Methode/Funktion nicht sofort aufruft, sondern den Call hinten an die Event-Queue von wxPython anhängt, d.h. es kann eine Verzögerung geben.

Re: Gui-Thread und Worker-Thread

Verfasst: Freitag 14. Oktober 2011, 14:57
von Schaf220
Dav1d hat geschrieben:Wie es aussieht hängt das mit wx.CallAfter zusammen (wenn man CallAfter weglässt, funktioniert es), das würde in so fern Sinn machen, da CallAfter die Methode/Funktion nicht sofort aufruft, sondern den Call hinten an die Event-Queue von wxPython anhängt, d.h. es kann eine Verzögerung geben.

Wie soll ich das denn weglassen?

Re: Gui-Thread und Worker-Thread

Verfasst: Freitag 14. Oktober 2011, 18:37
von BlackJack
@Schaf220: Ich sehe da eine Schleife in der Du vergessen hast `join` auch *aufzurufen*. Sollte es das gewesen sein? (Allerdings blockiert dass dann die GUI, oder!?)

Re: Gui-Thread und Worker-Thread

Verfasst: Freitag 14. Oktober 2011, 21:30
von Dav1d
@BlackJack: das macht keinen Unterschied, ich hab's lokal getestet und ja es würde die GUI blockieren, was wiederum bedeutet, dass "self.window.setListe" (CallAfter) nochmal später aufgerufen wird (wobei das in diesem Beispiel keinen Unterschied macht, da der Thread ja mit dieser Anweisung endet)

Du hast mehrere Möglichkeiten:
- es ignorieren, wenn die Mainloop nicht blockiert wird, werden die Daten halt etwas verzögert "ankommen" (merkt man nicht)
- pubsub verwenden
- eigene Events definieren und verwenden (mache ich gerne) (wx.lib.newevent und wx.PostEvent) (wobei das Event auch wieder am Ende der Queue angehängt wird, afair)

Ich persönlich würde bei der ersten Möglichkeit bleiben, wenn es nur _eine_ Funktion ist die so aufgerufen wird, falls es noch komplexer wird, tendiere ich zu eigenen Events oder pubsub (hab selber damit noch nicht wirklich gearbeitet), alleine schon wegen der Übersichtlichkeit.

Re: Gui-Thread und Worker-Thread

Verfasst: Montag 17. Oktober 2011, 07:56
von Schaf220
Danke erstmal für die Hilfe.

Habt ihr vielleicht ein gutes Tutorial/Beispiel für das Erstellen von eigenen Events?

Re: Gui-Thread und Worker-Thread

Verfasst: Montag 17. Oktober 2011, 12:25
von Dav1d
Das ist kein großes Ding, du kannst dir https://bitbucket.org/dav1d/mplayerctrl gerne als Vorbild nehmen.

Ein wichtiger Punkt zu Events, Command-Events erreichen auch die "Eltern/Parents" des Widgets, "normale" Events nur die "Kinder/Children", der einzige Unterschied beim Definieren ist:

Code: Alles auswählen

wx.lib.newevent.NewEvent() # "normales" event
wx.lib.newevent.NewCommandEvent() # command event
Ach im Wiki steht dazu auch noch was: http://wiki.wxpython.org/CustomEventClasses und http://wiki.wxpython.org/LongRunningTasks