Seite 1 von 1

Threading und wxPython

Verfasst: Donnerstag 20. April 2006, 09:08
von Francesco
Hallo,

würde zwar zum anderen Thema ("Probleme mit Threads") passen,
aber der Übersichtlichkeit starte ich einen neuen Thread über threads.
Verwirrend? :)

Könnte zwar auch zu den allgemeinen Fragen passen,
aber da hier soviele wx Sachen verwendet werden,
poste ich lieber hier.

Mit Threads kenne ich mich nicht so aus.
Ich habe das vorige Sample weitergestrickt.

Was mir nicht ganz klar ist:
1) Es gibt zwei Module (thread und threading).
Sind das manchmal doppelte Methoden (kann man von thread oder threading nehmen) oder hat jedes seine Berechtigung.

2) Es wird unten

Code: Alles auswählen

while not parent.confirmed:
      pass
verwendet, das natürlich nicht optimal ist, da es auch die ganze cpu
Leistung in Anspruch nimmt.
Robin Dunn meinte:
A better approach is to make the thread wait with a
threading.Event or similar.
If you have a threading.Event instance then you can make some
thread(s) to wait until the "event" happens with event.wait() and then
the thread(s) will wait until some other thread wants to signal the
event and calls event.set().

Das ist mir leider ein wenig zu hoch, bzw. habe mich mit Threads
wie gesagt, noch zu wenig auseinandergesetzt.

Wer kann mir helfen, das in das Programm einzubauen? :?

Danke im voraus!

Code: Alles auswählen

import wx
import time

from thread import start_new

EVT_RESULT_ID = wx.NewId()

def EVT_RESULT(win, func):
    win.Connect(-1, -1, EVT_RESULT_ID, func)

class ResultEvent(wx.PyEvent):
    def __init__(self, data):
        wx.PyEvent.__init__(self)
        self.SetEventType(EVT_RESULT_ID)
        self.data = data

def working(parent):
    wx.PostEvent(parent, ResultEvent(None))
    time.sleep(2)
    parent.confirmed = False
    wx.PostEvent(parent, ResultEvent(1))
    #waiting for user to confirm
    while not parent.confirmed:
      pass
    time.sleep(0.5)
    wx.CallAfter (parent.ShowMsg)


class MyApp(wx.App):
    def OnInit(self):
        frame = Myframe(None)
        self.SetTopWindow(frame)
        frame.CenterOnScreen()
        frame.Show(True)
        return True

class Myframe(wx.Frame):
    def __init__(self,parent):
        wx.Frame.__init__(self, parent, -1, 'Hello', size=(100,100))
        btn = wx.Button(self, -1, 'Click here', (10,10), (80,80))
        self.Bind(wx.EVT_BUTTON, lambda handler: start_new(working,(self,)), btn)
        EVT_RESULT(self,self.OnResult)
        self.busy = None
        self.confirmed = False
          
    def OnResult(self, event):
        if event.data is None:
            #self.busy = wx.BusyInfo("working in Background...")
            self.busy = wx.Dialog(self, title="Busy", pos=(300,300), size=(100,100))
            self.busy.Show(True)
        else:
            self.busy.Destroy()
            wx.MessageBox("Info", "Please Confirm!")
            self.confirmed = True
            
    def ShowMsg(self):
        wx.MessageBox("Info", "Finished")
    
x = MyApp(0)
x.MainLoop() 

Re: Threading und wxPython

Verfasst: Donnerstag 20. April 2006, 10:33
von gerold
Francesco hat geschrieben:Was mir nicht ganz klar ist:
1) Es gibt zwei Module (thread und threading).
Sind das manchmal doppelte Methoden (kann man von thread oder threading nehmen) oder hat jedes seine Berechtigung.
Hi Francesco!

Ich teile die Antworten mal auf. Sonst wird es zu viel.

Das Modul thread ist eher "lowlevel". Das Modul threading baut auf "thread" auf und erweitert es. Zum Beispiel gibt es im Modul "threading" sogenannte "Events", die aber nichts mit den Events aus wxPython gemeinsam haben. Ein Event ist eine Tür die offen sein kann, oder geschlossen. Man kann prüfen ob die Tür offen oder geschlossen ist, oder man stellt sich vor die Tür, bis diese aufgeht. Du kannst also die Ausführung deines Codes so lange warten lassen, bis die Tür aufgeht. Und das ohne Warteschleife. Ohne, dass du den Prozessor auslastest.

Ein Thread kann also stillgelegt werden bis, von einem anderen Thread aus (kann auch das Hauptprogramm sein), die Türe aufgemacht wird.

Das Aufmachen der Türe kann nicht vom eigenen Thread ausgehen, da dieser ja still steht.

Das sind die wichtigsten Anweisungen:

Code: Alles auswählen

event = threading.Event() # Event erstellen
event.set() # Tür öffnen
event.clear() # Tür schließen
event.isSet() # Ist die Tür offen?
event.wait # Warten bis die Tür geöffnet wird
Hier sind ein paar Beispiele zu diesem Thema: http://www.python-forum.de/topic-3869.html

lg
Gerold
:-)

Verfasst: Donnerstag 20. April 2006, 10:45
von pr0stAta
Sehr schön erklärt Gerold. Erinnert mich an die Bienen und die Blümchen :D
Ich kannte bis eben auch nicht den Unterschied und die Events.
Danke dafür!

Zu dem Beispiel: Mhm mit den von Gerold geposteten Codebeispieln
sollte man es ganz gut umschreiben können. Bin aber gerade auf der Arbeit :> Versuche später mal mein Glück

Verfasst: Donnerstag 20. April 2006, 10:55
von Francesco
pr0stAta hat geschrieben:Sehr schön erklärt Gerold. Erinnert mich an die Bienen und die Blümchen :D
Ich kannte bis eben auch nicht den Unterschied und die Events.
Danke dafür!
Da kann ich mir pr0stAta nur anschliessen.
Danke.

Re: Threading und wxPython

Verfasst: Donnerstag 20. April 2006, 11:02
von gerold
gerold hat geschrieben:Ich teile die Antworten mal auf. Sonst wird es zu viel.
Ohne Events --> Threads werden gleichzeitig ausgeführt

Klickt man mehrmals hintereinander auf den Button, dann laufen mehrere Threads gleichzeitig.

Code: Alles auswählen

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

# Ohne Events --> Threads werden gleichzeitig ausgeführt

import wx
import time
import thread


class MyFrame(wx.Frame):

    def __init__(self, parent = None):
        wx.Frame.__init__(self, parent, -1, "Mein Testframe")

        self.btn = wx.Button(self, -1, "Thread starten")
        self.btn.Bind(wx.EVT_BUTTON, self.thread_starten)
       
   
    def thread_starten(self, event = None):

        # Thread starten
        thread.start_new_thread(self.work, ())

   
    def work(self):

        # Hier wird Arbeit simuliert, die im Hintergrund läuft.
        
        # Normalerweise: 
        #     self.btn.SetBackgroundColour(wx.NullColor)
        # Stattdessen wird der Aufruf an **CallAfter()** übergeben.
        # So kann von diesem Thread aus auf Funktionen des Hauptthreads
        # zugegriffen werden.
        wx.CallAfter(self.btn.SetBackgroundColour, wx.NullColor)
        
        time.sleep(3)
        
        # Normalerweise: 
        #     self.btn.SetBackgroundColour("green")
        # Stattdessen wird der Aufruf an **CallAfter()** übergeben
        # So kann von diesem Thread aus auf Funktionen des Hauptthreads
        # zugegriffen werden.
        wx.CallAfter(self.btn.SetBackgroundColour, "green")
        
        time.sleep(3)
       
        # Nachricht ausgeben
        # Normalerweise: 
        #     wx.MessageDialog(
        #         self, "Task Finished", style=wx.OK | wx.CENTRE
        #     ).ShowModal()
        # Stattdessen wird der Aufruf an **CallAfter()** übergeben
        # So kann von diesem Thread aus auf Funktionen des Hauptthreads
        # zugegriffen werden.
        diag = wx.MessageDialog(self, "Task Finished", style=wx.OK | wx.CENTRE)
        wx.CallAfter(diag.ShowModal)


class MyApp(wx.App):

    def OnInit(self):
        frame = MyFrame()
        frame.Show()
        self.MainLoop()
        return True


if __name__ == "__main__":
    app = MyApp()
lg
Gerold
:-)

Edit: Ich habe den Code verändert, dass er unter Linux und Windows gleichermaßen funktioniert. Das Zaubermittel dafür heißt wx.CallAfter()

Verfasst: Donnerstag 20. April 2006, 11:40
von gerold
Hi!

Ich habe jetzt lange an einem Beispiel mit "Events" gearbeitet und habe es soeben wieder verschmissen. Egal wieviele Events ich in eine Liste lege und wie kompliziert ich es mache. Events sind nicht dafür geeignet, Threads **hintereinander** abarbeiten zu lassen.

Deshalb greife ich jetzt auf Queue-Objekte zurück. Auch die Verwendung dieser Objekte ist hier http://www.python-forum.de/post-32524.html#32524 beschrieben.

Aber noch ein paar Worte zu Queue, bevor ich das Beispiel poste:

Eine Queue ist eine Liste von Objekten mit den Vorzügen von Events. Man kann Objekte zur Liste hinzufügen (an den Anfang) und Objekte wieder herausnehmen (vom Ende). Das ist so etwas wie eine First-In-First-Out-Liste. Oder besser gesagt eine Warteliste.

Das wäre nichts besonderes, da man das auch mit normalen Listen machen kann.

Code: Alles auswählen

my_list = []
my_list.insert(0, new_element)
next_element = my_list.pop()
Der Vorteil ist der, dass man die Ausführung des Codes so lange anhalten lassen kann, bis etwas in der Liste ist, das man wieder aus der Liste herausnehmen kann.

Ist die Queue leer, dann bleibt der Thread so lange stehen, bis von einem anderen Thread aus etwas in die Liste hinzugefügt wurde. Wenn das Auslesen dieser Queue in einer Schleife passiert, dann kann dadurch sichergestellt werden, dass wirklich alles **hintereinander** abgearbeitet wird. Voraussetzung ist natürlich, dass die Queue nur von einem einzigen Thread abgearbeitet wird.

Hier die wichtigsten Anweisungen der Queue:

Code: Alles auswählen

queue = Queue.Queue() # erstellt die Queue
queue.put("neuer Eintrag") # Gibt ein Element in die Queue
new_element = queue.get() # **Wartet** bis es etwas zu holen gibt und 
                          # gibt das jeweils älteste Objekt zurück.
queue.empty() # Prüft ob etwas in der Queue steht
lg
Gerold
:-)

Verfasst: Donnerstag 20. April 2006, 12:01
von gerold
Und hier das Beispiel mit der Queue.

Es wird dadurch sicher gestellt, dass die Ausführung **hintereinander** passiert und nicht wie bei mehreren Threads üblich, gleichzeitig.

Code: Alles auswählen

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

import wx
import time
import thread
import Queue


class MyFrame(wx.Frame):

    def __init__(self, parent = None):
        wx.Frame.__init__(
            self, parent, -1, "Mein Testframe", size = wx.Size(200, 100)
        )

        self.btn = wx.Button(self, -1, "Neuer Durchlauf")
        self.btn.Bind(wx.EVT_BUTTON, self.fill_queue)
       
        self.queue = Queue.Queue()

        # Thread starten
        thread.start_new_thread(self.worker, ())

        # Sicher gehen, dass der Thread korrekt geschlossen wird
        self.Bind(wx.EVT_CLOSE, self.close_worker)


    def worker(self):
        while True:
            # Hier wird gewartet bis etwas in der Queue steht
            s = self.queue.get()
            # Um den Thread korrekt beenden zu können...
            if s == "quit":
                return

            # Hier wird Arbeit simuliert, die im Hintergrund läuft.
            #self.btn.SetBackgroundColour(wx.NullColor)
            wx.CallAfter(self.btn.SetBackgroundColour, wx.NullColor)
            
            time.sleep(3)
            
            #self.btn.SetBackgroundColour("green")
            wx.CallAfter(self.btn.SetBackgroundColour, "green")
            
            time.sleep(3)
           
            # Nachricht ausgeben
            #wx.MessageDialog(self, "Task Finished", style=wx.OK | wx.CENTRE).ShowModal()
            diag = wx.MessageDialog(self, "Task Finished", style=wx.OK | wx.CENTRE)
            wx.CallAfter(diag.ShowModal)


    def fill_queue(self, event = None):
        self.queue.put("irgendetwas")
       
       
    def close_worker(self, event = None):
        self.queue.put("quit")
        event.Skip()


class MyApp(wx.App):

    def OnInit(self):
        frame = MyFrame()
        frame.Show()
        self.MainLoop()
        return True


if __name__ == "__main__":
    app = MyApp()
lg
Gerold
:-)

Edit: Dieses Beispiel funktionierte nicht korrekt unter Linux. Die Funktionen des Hauptthreads werden jetzt über die Proxy-Funktion wx.CallAfter() aufgerufen. So wird der arbeitende Thread vom Hauptthread abgeschirmt. Kein Thread kommt so dem anderen in die Quere. :-)

Re: Threading und wxPython

Verfasst: Donnerstag 20. April 2006, 12:29
von Francesco
Toll!!
Danke für deinen Kurs bis jetzt.

Re: Threading und wxPython

Verfasst: Sonntag 4. Juni 2006, 19:45
von gerold
gerold hat geschrieben:Ohne Events --> Threads werden gleichzeitig ausgeführt

Klickt man mehrmals hintereinander auf den Button, dann laufen mehrere Threads gleichzeitig.
Hi @all!

Das Beispiel das ich hier http://www.python-forum.de/post-35415.html#35415 geschrieben hatte, funktionierte unter Linux nicht so wie gewünscht.

Ich habe den Code verändert. Das Zaubermittel für Threads heißt wx.CallAfter(). Damit kann man von einem Worker-Thread aus Funktionen des Haupt-Threads aufrufen. wx.CallAfter() kümmert sich um die korrekte Trennung der Threads.

lg
Gerold
:-)