Return-Wert aus Multiprocessing bekommen

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.
Antworten
lordzwieback
User
Beiträge: 55
Registriert: Montag 2. März 2015, 14:35
Kontaktdaten:

Hallo,

ich versuche mich gerade an einem Script, in welchem ich Multiprocessing anwenden möchte. Habe mich ein wenig eingelesen und ein kleines Beispiel gebaut, das auch soweit funktioniert. Im Prinzip geht es darum, eine Funktion auszuführen und danach fünf Sekunden zu warten. Ist die Funktion bis zum Ablaufen der Wartezeit nicht fertig, wird sie terminiert.

Momentaner Stand ist folgend:

Funktion:

Code: Alles auswählen

def myFunction(pc, user):
    temp = subprocess.Popen([r"C:\Windows\SysNative\query.exe",
                             "user", user,
                             "/server",
                             pc],
                            stdout=subprocess.PIPE).communicate()[0]
Main:

Code: Alles auswählen

def main():
    local_network_user = {
        "PC1": "A*******",
        "PC2": "R*****",
        "PC3": "D***",
        "PC4": "S*********"
    }

    for key in local_network_user:
        # start function 'myFunction' as process
        process = Process(target=myFunction, args=(key, local_network_user.get(key)))
        process.start()
        print("Process started...")

        # wait for 5 seconds or until process finishes
        process.join(5)
        print("Waiting...")

        # if thread is still active
        if process.is_alive():
            print("running... let's kill the process...")
            process.terminate()
            process.join()
So weit so gut, der Prozess wird gestartet, terminiert bei > 5 Sekunden und ansonsten führt er den Befehl aus. Nun brauche ich im realen Script aber den Rückgabewert (Variable 'temp' aus Funktion 'myFuction') und verstehe einfach nicht, wie das funktionieren soll. Habe mir schon einige Beispiele mit multiprocessing.Manager, Value, shared variables usw. angeschaut, aber ich bin wohl zu doof dafür. :( Kann mir hier irgendwer verständlich erklären, was ich tun muss, um den Rückgabewert aus der Funktion herauszubekommen? Zurückkommen sollte da ein String.

Danke schonmal fürs Lesen und Grüße,
lordzwieback
__deets__
User
Beiträge: 14545
Registriert: Mittwoch 14. Oktober 2015, 14:29

Zuerstmal bringt dir dein Vorgehen doch genau gar nix. Mühselig mit multiprocessing einen anderen Prozess hochzuziehen, auf den du dann aber direkt wartest - da kannst du deine Query doch gleich direkt starten :K

Und konzeptionell kannst du keine Rückgabe bekommen, wenn du den Prozess killst. Das ist dann undefiniert, es weiß doch keiner, wie weit der Prozess gekommen ist.

Wenn du den NICHT umbringst, dann musst du den Wert in temp natürlich auch per return zurückgeben. Allerdings funktioniert das dann trotzdem nicht mit Process, das ist zu wenig. Du musst da eigentlich noch eine queue oder andere geteilte Datenstruktur mit anlegen. Einfacher ist gleich der Umgang mit einem Pool, und zb dessen map Methode. KIllen darfst du dann aber nicht, sondern musst (was ich denke das ist, das du eigentlich willst) den query subprocess töten.
lordzwieback
User
Beiträge: 55
Registriert: Montag 2. März 2015, 14:35
Kontaktdaten:

Zuerstmal danke für die Hinweise und Tipps.

Dass ich keine Rückgabe erhalte, wenn ich den Prozess kille ist mir jetzt auch aufgefallen. Evtl wird der Prozess ja schon abgebrochen, bevor die Zuweisung von temp in das dict geschieht.

Nochmal zum Wunschablauf:
Der query subprocess wird angeschmissen. Diesen möchte ich, sollte er nach 5 Sekunden keine Rückgabe liefern abbrechen. Grund hierfür: Wird ein Rechner, der angeschaltet ist angesprochen, erhalte ich eine Rückgabe in unter 1 Sekunde. Ist der Rechner ausgeschaltet, dauert der Durchlauf des Befehls (pro Rechner!) bis zu 25 Sekunden. Somit könnte ich pro Komplettdurchlauf des Skripts eine "halbe Ewigkeit" an Zeit sparen.

Deinen Vorschlag mit dem Pool habe ich auch schon entdeckt, nur bin ich da noch nicht ganz dahintergestiegen. Ich werde mir das aber nochmal genauer anschauen.

Und im Prinzip hast du recht, es geht nur darum, den subprocess nach der Wartezeit abzubrechen.
__deets__
User
Beiträge: 14545
Registriert: Mittwoch 14. Oktober 2015, 14:29

Wenn es nur darum geht, den abzubrechen, brauchst du zwar subprocess, aber nicht multiprocessing. Und dort einfach run, das kann schon timeout: https://docs.python.org/3/library/subprocess.html
lordzwieback
User
Beiträge: 55
Registriert: Montag 2. März 2015, 14:35
Kontaktdaten:

Das mit dem 'run' Befehl war schonmal hilfreich. Es läuft jetzt fast so, wie ich es benötige.

Code: Alles auswählen

def main():
    local_network_user = {
        "PC1": "A*******",
        "PC2": "R*****",
        "PC3": "D***",
        "PC4": "S*********"
    }

    for key in local_network_user:
       print(key + "/" + local_network_user.get(key))
        try:
            output = subprocess.run([r"C:\Windows\SysNative\query.exe", "user", local_network_user.get(key), "/server", key], timeout=5)
            print("ERGEBNIS: " + str(output))
        except subprocess.TimeoutExpired:
            print("TIMEOUT USER " + local_network_user.get(key))
            continue
Wenn ein Timeout auftaucht, springt er in die Exception und schreibt die Zeile TIMEOUT...

Wenn alles glatt läuft, kommt zwar der ERGEBNIS-print, aber nicht mehr wie vorher mit dem Inhalt, den der Befehl zurückgibt, sondern folgendes:
ERGEBNIS: CompletedProcess(args=['C:\\Windows\\SysNative\\query.exe', 'user', 'A******', '/server', 'PC1'], returncode=1)

Habe dann nochmal unter dem Doc-Link, den du geschickt hattest nachgeschaut. Bin dann auf class subprocess.CompletedProcess (The return value from run(), representing a process that has finished.) gestoßen.

Darunter dann folgendes gefunden:
stdout
Captured stdout from the child process. A bytes sequence, or a string if run() was called with an encoding or errors. None if stdout was not captured.

If you ran the process with stderr=subprocess.STDOUT, stdout and stderr will be combined in this attribute, and stderr will be None.


Nur kann ich weder stdout= weder stderr= als Parameter beim run() Befehl nehmen und wenn ich print("ERGEBNIS: " + output.stdout) teste, erhalte ich immer "None" als Ausgabe. Muss ich dann doch wieder eine andere Herangehensweise finden?
__deets__
User
Beiträge: 14545
Registriert: Mittwoch 14. Oktober 2015, 14:29

Doch, die kannst du beide angeben. Steht klar in der dokumentierten Signatur der Funktion.
lordzwieback
User
Beiträge: 55
Registriert: Montag 2. März 2015, 14:35
Kontaktdaten:

Moinmoin,

ich habs gefunden, hattest recht.

Habe es jetzt folgendermaßen laut Doc umgeschrieben:
Zu subprocess.run
This does not capture stdout or stderr by default. To do so, pass PIPE for the stdout and/or stderr arguments.

Code: Alles auswählen

        try:
            start = datetime.now()
            output = subprocess.run([r"C:\Windows\SysNative\query.exe", "user", local_network_user.get(key), "/server", key], stdout=subprocess.PIPE, timeout=5)
            print("ERGEBNIS: " + str(output.stdout))
            print("KEIN TIMEOUT ZEIT:" + str(datetime.now() - start))
        except subprocess.TimeoutExpired:
            print("TIMEOUT USER " + local_network_user.get(key))
            print("TIMEOUT ZEIT: " + str(datetime.now() - start))
            continue
Jetzt erhalte ich die Ausgabe des query Befehls und kann diesen korrekt ausgeben. Aber leider ergibt die Zeitmessung, die ich hier jetzt noch zusätzlich drinhabe, dass der Timeout nicht nach 5 Sekunden, sondern erst nach 20 - 25 Sekunden kommt.

Hier einige print-Ausgaben aus der Console:
TIMEOUT ZEIT: 0:00:21.050983
KEIN TIMEOUT ZEIT: 0:00:00.097088
TIMEOUT ZEIT: 0:00:21.068138
TIMEOUT ZEIT: 0:00:21.070725
KEIN TIMEOUT ZEIT: 0:00:00.086738

Was mache ich denn hier falsch?! :(

EDIT:
Wenn ich den Parameter stdout=subprocess.PIPE wieder herausnehme, kommt folgendes dabei raus:
KEIN TIMEOUT ZEIT: 0:00:00.112095
KEIN TIMEOUT ZEIT: 0:00:00.091930
TIMEOUT ZEIT: 0:00:05.010635
TIMEOUT ZEIT: 0:00:05.014957
TIMEOUT ZEIT: 0:00:05.014663

Bewirkt der stdout-Parameter, dass der Befehl zwingend durchgeführt werden muss (und ignoriert somit den Timeout-Parameter)?
lordzwieback
User
Beiträge: 55
Registriert: Montag 2. März 2015, 14:35
Kontaktdaten:

Ich habe wohl die Lösung für mein Problem gefunden.

Code: Alles auswählen

        cmd = [r"C:\Windows\SysNative\query.exe", "user", local_network_user.get(key), "/server", key]
        try:
            start = datetime.now()
            proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
            stdout, stderr = proc.communicate(timeout=5)
            print("OUTPUT:" + str(stdout))
        except subprocess.TimeoutExpired:
            print("TERMINATED AFTER: " + str(datetime.now() - start))
Der Timeout als Parameter von communicate() funktioniert. Wenn der Timeout nicht auftritt, wird korrekt der Output ausgegeben bzw in die Variable stdout geschrieben und kann dann von dort aus weiter bearbeitet werden.
__deets__
User
Beiträge: 14545
Registriert: Mittwoch 14. Oktober 2015, 14:29

Schoen das es klappt. Das der run-Aufruf sich so komisch verhaelt ist ungewoehnlich, das haette ich nicht erwartet. Bei Gelegenheit schaue ich mir das mal an. Ggf. kannst du natuerlich auch einen Bug-Report einstellen im Python bug-tracker!
lordzwieback
User
Beiträge: 55
Registriert: Montag 2. März 2015, 14:35
Kontaktdaten:

Ich nehme an, dass der query Befehl weitere Child-Prozesse startet und dadurch nicht einfach beendet werden kann - ist aber nur eine Vermutung. Die beruht darauf, dass ich (als ich noch den run() Befehl drin hatte) den query durch einen einfachen ping ausgetauscht hatte. Das hat wunderbar funktioniert.

Was weiterhin noch komisch war:
Der Popen() enthält bei meinem PyCharm (aktuellste Version mit Python 3.5) beim Parametervorschlag den timeout. In der offiziellen Doku zum Popen steht der timeout Parameter nicht mehr in den verfügbaren Parametern. Wenn ich den dann ausgeführt habe, hat er mir auch angezeigt etwa... "unknown parameter 'timeout' ".

Bin danach dann durch Zufall auf den .communicate(timeout=5) gestoßen, das hat dann funktioniert.
Antworten