Gui-Thread und Worker-Thread

Plattformunabhängige GUIs mit wxWidgets.
Antworten
Benutzeravatar
Schaf220
User
Beiträge: 113
Registriert: Montag 11. August 2008, 16:00
Wohnort: Bremen
Kontaktdaten:

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
Dav1d
User
Beiträge: 1437
Registriert: Donnerstag 30. Juli 2009, 12:03
Kontaktdaten:

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.
the more they change the more they stay the same
Benutzeravatar
Schaf220
User
Beiträge: 113
Registriert: Montag 11. August 2008, 16:00
Wohnort: Bremen
Kontaktdaten:

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?
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!?)
Dav1d
User
Beiträge: 1437
Registriert: Donnerstag 30. Juli 2009, 12:03
Kontaktdaten:

@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.
the more they change the more they stay the same
Benutzeravatar
Schaf220
User
Beiträge: 113
Registriert: Montag 11. August 2008, 16:00
Wohnort: Bremen
Kontaktdaten:

Danke erstmal für die Hilfe.

Habt ihr vielleicht ein gutes Tutorial/Beispiel für das Erstellen von eigenen Events?
Dav1d
User
Beiträge: 1437
Registriert: Donnerstag 30. Juli 2009, 12:03
Kontaktdaten:

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
the more they change the more they stay the same
Antworten