Thread überwachen

Wenn du dir nicht sicher bist, in welchem der anderen Foren du die Frage stellen sollst, dann bist du hier im Forum für allgemeine Fragen sicher richtig.
Buell
User
Beiträge: 90
Registriert: Samstag 29. Oktober 2005, 14:17

Montag 6. März 2006, 19:53

Hallo,

folgendes Problem: Mit thread.start_new_thread(_MessagePump,()) starte ich einen Thread. Ich bin nun auf der Suche nach etwas, mit dem ich eine Methode aufrufen kann, wenn der Thread beendet ist.

Hintergrund: Im obigen Thread läuft ein win32event.MsgWaitForMultipleObjects. Bei bestimmten Events soll eine Funktion einer COM Schnittstelle aufgrufen werden. Dies kann ich allerdings nicht aus dem Thread heraus tun. Man könnte eine KontrollVariable deklarieren, und eine While-Schleife mit einem Sleep(0.5) laufen lassen und so die Variable überwachen (bei Änderung -> führe aus) das ist allerdings keine schöne Lösung weil die gesamte Anwendung dabei in sleep versetzt wird.
Also das mit den Events funktioniert, nur wie gesagt kann ich die Funktion der COM Schnittstelle nicht aus einem Thread heraus öffnen, brauche aber diesen Thread um auf die Events zu warten...

einer ne Idee?
mitsuhiko
User
Beiträge: 1790
Registriert: Donnerstag 28. Oktober 2004, 16:33
Wohnort: Graz, Steiermark - Österreich
Kontaktdaten:

Montag 6. März 2006, 20:53

Schau dir mal threading.Thread an. Das ist eine Klasse für einfaches Threading. Dürfte in deinem Fall ganz nützlich sein.
TUFKAB – the user formerly known as blackbird
Benutzeravatar
gerold
Python-Forum Veteran
Beiträge: 5555
Registriert: Samstag 28. Februar 2004, 22:04
Wohnort: Oberhofen im Inntal (Tirol)
Kontaktdaten:

Montag 6. März 2006, 22:24

Buell hat geschrieben:Man könnte eine KontrollVariable deklarieren
Hi Buell!

Schon mal dieses Beispiel angesehen? http://www.python-forum.de/viewtopic.php?p=30311#30311
Vielleicht kannst du mit "events" arbeiten. Man kann damit eine Schleife so lange blockieren, bis die "event"-Variable auf True gesetzt wird. Dabei wird nur der Thread blockiert.

mfg
Gerold
:-)
http://halvar.at | Kleiner Bascom AVR Kurs
Wissen hat eine wunderbare Eigenschaft: Es verdoppelt sich, wenn man es teilt.
Buell
User
Beiträge: 90
Registriert: Samstag 29. Oktober 2005, 14:17

Dienstag 7. März 2006, 10:02

erstmal danke für die antworten, werde mir mal eure Vorschläge anschauen
Buell
User
Beiträge: 90
Registriert: Samstag 29. Oktober 2005, 14:17

Dienstag 7. März 2006, 10:32

Also,

wenn ich das 2. Beispiel richtig verstehe, dann wird mit hauptschalter.wait() so lange der Ablauf des Programmes unterbrochen, bis das Event im Thread gesetzt wurde. Wenn das so ist, ist das nicht das was ich suche, denn der Rest darf nicht "aufgehalten" werden. Der Thread läuft in einem win32com.server und auf dessen Methoden wird ständig "von außen" zugegriffen. Ein wait würde den Ablauf der angebundenen Module behindern.

Was mir vorschwebt ist schon ähnlich, nur dass auf dieses Event nicht extra gewartet werden muss, sondern wie bei einem Button Event meinetwegen eine Methode ausgeführt wird.
Benutzeravatar
gerold
Python-Forum Veteran
Beiträge: 5555
Registriert: Samstag 28. Februar 2004, 22:04
Wohnort: Oberhofen im Inntal (Tirol)
Kontaktdaten:

Dienstag 7. März 2006, 10:55

Buell hat geschrieben:dann wird mit hauptschalter.wait() so lange der Ablauf des Programmes unterbrochen, bis das Event im Thread gesetzt wurde.
Hi Buell!

Irgendwo muss gewartet werden, falls das Programm nicht ständig beschäftigt ist, so dass es nicht von selbst beendet wird. Das ist auch bei Tkinter oder anderen GUIs der Fall. Denke nur an root.mainloop() und Co.

Du selbst musst bestimmen, wo gewartet wird. Die Threads beenden sich, sobald das Hauptprogramm beendet wird. Auch ohne Threads musstest du ja schon vorher irgendwo warten, oder nicht?

Was macht dein Programm? Vielleicht finden wir eine bessere Lösung. Was ich aber noch nicht verstehe ist, warum du vom Thread aus keine andere Funktion aufrufen kannst. Gibt es dazu eine Fehlermeldung?

Könntest du an die Thread-Funktion nicht einfach die Klasseninstanz übergeben, in der die Methode, die aufgerufen werden soll, existiert?

Oder überhaupt nur die Funktion, die aufgerufen werden soll, übergeben. Die Klasseninstanz ist ja in dem Fall nicht wichtig, oder?

mfg
Gerold
:-)
http://halvar.at | Kleiner Bascom AVR Kurs
Wissen hat eine wunderbare Eigenschaft: Es verdoppelt sich, wenn man es teilt.
Benutzeravatar
gerold
Python-Forum Veteran
Beiträge: 5555
Registriert: Samstag 28. Februar 2004, 22:04
Wohnort: Oberhofen im Inntal (Tirol)
Kontaktdaten:

Dienstag 7. März 2006, 11:10

So auf die Art z.B.:

Code: Alles auswählen

from threading import thread

class Hallo:
    def seavas(self):
        print "Seavas"
        
h = Hallo()
f = h.seavas

thread.start_new_thread(_MessagePump, (f,))
mfg
Gerold
:-)
http://halvar.at | Kleiner Bascom AVR Kurs
Wissen hat eine wunderbare Eigenschaft: Es verdoppelt sich, wenn man es teilt.
Buell
User
Beiträge: 90
Registriert: Samstag 29. Oktober 2005, 14:17

Dienstag 7. März 2006, 11:32

Also das ganz ist etwas komplex.

Ich versuche mal grob das Programm zu beschreiben. Wie schon gesagt handelt es sich um einen win32com.server der mir als Schnittstelle zwischen Anwendung und Windows COM Schnittstellen dient. Die COM Schnittstellen sind dabei Sapi4 und Sapi5.1 (SpeechRecognition) sowie die COM Schnittstelle des MSAgent. genau um diese dreht es sich gerade.

Im win32com.server existieren 4 Klassen, eine für TextToSpeech, eine für SpeechRecognition, eine für den MSAgent und eine um die anderen 3 zu starten. Die Sache ist, dass ich von den ersten beiden klassen mit win32event sozusagen events an die MSAgent Klasse schicke. Um auf diese Events zu warten wird in einem Thread in einer Endlosschleife ein win32event.MsgWaitForMultipleObjects() ausgeführt. Dieser Thread läuft in der Klasse MSAgent. Wird ein Event gesetzt wird aus dem Thread heraus eine Methode in der Klasse MSAgent aufgerufen. Soweit funktioniert das auch. Das Problem ist, dass die Methode quasi dann im Thread ausgeführt wird und das darf nicht sein. In der Methode wird auf die COM-Schnittstelle des MSAgent zugegriffen aber das kann man nur aus dem Thread heraus, aus dem die COM Schnittstelle geöffnet wurde.

Fehlermeldung:

Code: Alles auswählen

pywintypes.com_error: (-2147352567, 'Ausnahmefehler aufgetreten.', (0, 'Agent.Control.2', 'Eine Schnittstelle, die f\xfcr einen anderen Thread marshalled war, wurde von der Anwendung aufgerufen.\r\n', None, 0, -2147417842), None)
Einen Punkt in dem im "Hauptthread" auf irgendwas gewartet wird kann ich nicht nutzen, da es sich wie gesagt nur um den win32com.server handelt. Dieser wird am Anfang vom eigentlichen Hauptptogramm per dispatch eingebunden/gestartet. Würde da jetzt ein "Wartepunkt" drin sein, dann würde auch das Hauptprogramm stehen bleiben und gar nicht erst das GUI anzeigen.
Den MSAgent komplett in einem eigenen Thread zu sterten um somit ein wait einbauen zu können kann ich auch nicht, da in python eine COM Schnittstelle nicht aus einem "Unterthread" heraus geöffnet werden kann.

Hier mal die Klasse des MSAgent, die COM Schnittstelle wird im Untermodul MSAgent geöffnet:

Code: Alles auswählen

class MSAgent:
    _reg_clsid_ = "{06BAFDBD-B98F-4FA2-9BE0-54C6927F9911}"
    _reg_desc_ = "Python Speech Project MSAgent COM Server"
    _reg_progid_ = "Python.MSAgentServer" # This is the name of the Server!!!!!
    _reg_class_spec_ = "SpeechServer.MSAgent"
    _public_methods_ = ['Hello','StartAgent','StopAgent','GetStatus','PlayAnimation']
    _public_attrs_ = ['softspace', 'noCalls','Merlin']
    _readonly_attrs_ = ['noCalls']

    def __init__(self):
        self.softspace = 1
        self.noCalls = 0
        self.status = "stopped"
        self.ConnectAgent()
        thread.start_new_thread(self._MessagePump,())

    def ChangeStatus(self, status):
        self.status = status
        return None

    def Hello(self, who):
        self.noCalls = self.noCalls + 1

        # insert "softspace" number of spaces
        return "Hello" + " " * self.softspace + who

    def StartAgent(self):
        if self.status == "stopped":
            self.Agent.StartAgent()
        self.status = "started"
        return True

    def StopAgent(self):
        if self.status == "started":
            self.Agent.StopAgent()
        self.status = "stopped"
        return True

    def GetStatus(self):
        return self.status

    def ConnectAgent(self):
        self.Agent_import = __import__("MSAgent")
        self.Agent = self.Agent_import.MyAgent()
        return None

    def PlayAnimation(self):
        print "ani "+self.status
        if self.status == "started":
            self.Agent.PlayAnimation('Greet')
        return None

    def GetAnimations(self):
        names = self.Agent.GetAnimations()
        return names

    def _MessagePump(self):

        while 1:
            rc = win32event.MsgWaitForMultipleObjects(
                (AgentAnimation,AgentAnimationN), 
                0, # wait for all = false
                TIMEOUT,  #  (or win32event.INFINITE)
                win32event.QS_ALLEVENTS) # type of input

		# You can call a function here if it doesn't take too long.
		#   It will get executed *at least* every 200ms -- possibly
		#   a lot more, depending on the number of windows messages received.

            if rc == win32event.WAIT_OBJECT_0:
			# Our first event listed "TextToSpeechStart" was triggered.
			# Someone wants us to exit.
                a = self.PlayAnimation()

            if rc == win32event.WAIT_OBJECT_0+1:
			# Our first event listed "TextToSpeechStart" was triggered.
			# Someone wants us to exit.
                a = self.PlayAnimation()

            elif rc == win32event.WAIT_TIMEOUT:
			# Our timeout has elapsed.
			# Do some work here (e.g, poll something can you can't thread)
			#   or just feel good to be alive.
			# Good place to call watchdog(). (Editor's note: See my "thread lifetime" recepie.)
                pass
            else:
                raise RuntimeError( "unexpected win32wait return value")
MSAgent Untermodul:

Code: Alles auswählen

import win32com.client
from win32com.client import *
#import pyCfg

# Registry key for MsAgent
# gencache.EnsureModule('{F5BE8BD2-7DE6-11D0-91FE-00C04FD701A5}', 0, 1, 0)


class MyAgent:
    def __init__(self, eventClass=None):
        # initial Agent:
        self.Agent = win32com.client.Dispatch("Agent.Control.2")
        self.Agent.Connected = 1
        self.Agent.Characters.Load("Merlin","c:\windows\msagent\chars\merlin.acs")
        self.Merlin = self.Agent.Characters("Merlin")
        self.Animation = ""

    def StartAgent(self):
        self.Merlin.Show()
        return None

    def StopAgent(self):
        self.Merlin.Hide()
        return None

    def AgentSpeak(self, text):
        self.Merlin.Speak(text)
        self.Merlin.Blink()
        return None

    def PlayAnimation(self, animation):
        self.Merlin.Play(animation)
        self.Animation = animation
        print "Hallo"
        return None

    def GetAnimations(self):
        Animations = list(self.Merlin.AnimationNames)
        names = []
        animation_no = 1
        try:
            while animation_no:
                names.append(Animations[animation_no])
                animation_no += 1
        except:
            print "Alle Animationen gefunden!"
        return names
PS: Der fehler wird erzeugt im Untermodul in Zeile 32, aber die Ursache dafür liegt darin, dass die Funktion self.Merlin.Play() aus einem anderen Thread heraus aufgerufen wird.
Mad-Marty
User
Beiträge: 317
Registriert: Mittwoch 18. Januar 2006, 19:46

Donnerstag 9. März 2006, 20:04

erscheint mir für das messagepump übertrieben,

ein 2ter thread braucht einfach nur auf Multiple objects zu warten, und könnte mit einer methode Trigger() eine variable auf True setzen, welche der andere thread konstant prüft, und wenn True halt irgendwas macht ...


das ganze kostet nur wenige zeilen.


EDIT: ups, kein com in anderen threads möglich ? das erschwert das ganze
Buell
User
Beiträge: 90
Registriert: Samstag 29. Oktober 2005, 14:17

Freitag 10. März 2006, 08:28

Mad-Marty hat geschrieben:EDIT: ups, kein com in anderen threads möglich ? das erschwert das ganze
... genau das ist das Problem
Benutzeravatar
gerold
Python-Forum Veteran
Beiträge: 5555
Registriert: Samstag 28. Februar 2004, 22:04
Wohnort: Oberhofen im Inntal (Tirol)
Kontaktdaten:

Freitag 10. März 2006, 09:19

Buell hat geschrieben:die Ursache dafür liegt darin, dass die Funktion self.Merlin.Play() aus einem anderen Thread heraus aufgerufen wird.
Hi Buell!

Ich verstehe nicht, warum mein Queue-Beispielnicht auf deinen Fall anwendbar sein soll.

win32com.client.Dispatch("Agent.Control.2") würde in einem eigenen Thread laufen. Da dieser Thread *nur* die Anweisung "Führe jetzt die Funktion PlayAnimation aus" bekommt, aber die Funktion trotzdem noch selber ausführen muss...

Agent.Control.2 würde nie mitbekommen, dass die Anweisung von einem anderen Thread kommt.

mfg
Gerold
:-)
http://halvar.at | Kleiner Bascom AVR Kurs
Wissen hat eine wunderbare Eigenschaft: Es verdoppelt sich, wenn man es teilt.
Buell
User
Beiträge: 90
Registriert: Samstag 29. Oktober 2005, 14:17

Freitag 10. März 2006, 20:16

gerold hat geschrieben:Ich verstehe nicht, warum mein Queue-Beispiel nicht auf deinen Fall anwendbar sein soll.
... weil du den Dispatch nicht in einem Client-Thread machen kannst. Du kannst die COM Schnittstelle nur aus dem Hauptthread öffnen. Wenn es gehen würde, würde dein Queue-Beispiel sicherlich funktionieren, aber dann würde auch mein Beispiel laufen.

Trotzdem Danke!
modelnine
User
Beiträge: 670
Registriert: Sonntag 15. Januar 2006, 18:42
Wohnort: Celle
Kontaktdaten:

Freitag 10. März 2006, 20:29

Ganz davon abgesehen: ein CoInitialize() in dem zweiten Thread würde COM auch für den zweiten Thread zur Verfügung stellen. Zumindest hat man mir das mal gesagt. Das man COM also nur aus einem Thread bedienen kann scheint mir zweifelhaft. (was natürlich nicht bedeutet dass man die Objekte verteilen kann, dafür brauchts dann MessagePassing ala Queue).
--- Heiko.
Benutzeravatar
gerold
Python-Forum Veteran
Beiträge: 5555
Registriert: Samstag 28. Februar 2004, 22:04
Wohnort: Oberhofen im Inntal (Tirol)
Kontaktdaten:

Freitag 10. März 2006, 20:33

Buell hat geschrieben:... weil du den Dispatch nicht in einem Client-Thread machen kannst. Du kannst die COM Schnittstelle nur aus dem Hauptthread öffnen.
Hi Buell!

Dann könnte dir evt. dieser Thread helfen. Weiter hinten beschreibt modelnine, wie man zwei Prozesse über STDIN und STDOUT kommunizieren lassen kann. http://www.python-forum.de/viewtopic.php?t=4941

Damit wären nicht mehr zwei Threads sonder zwei Prozesse im Spiel.

mfg
Gerold
:-)
http://halvar.at | Kleiner Bascom AVR Kurs
Wissen hat eine wunderbare Eigenschaft: Es verdoppelt sich, wenn man es teilt.
Buell
User
Beiträge: 90
Registriert: Samstag 29. Oktober 2005, 14:17

Freitag 10. März 2006, 20:37

Vielen Dank, schau ich mir gleich mal an.
Antworten