it-swarm.com.de

Woher weiß ich in Python, wann ein Prozess abgeschlossen ist?

Aus einer Python-GUI (PyGTK) starte ich einen Prozess (mit Multiprocessing). Der Vorgang dauert sehr lange (~ 20 Minuten). Wenn der Prozess abgeschlossen ist, möchte ich ihn bereinigen (extrahieren Sie die Ergebnisse und schließen Sie sich dem Prozess an). Woher weiß ich, wann der Vorgang abgeschlossen ist?

Mein Kollege schlug eine Besetztschleife innerhalb des übergeordneten Prozesses vor, die prüft, ob der untergeordnete Prozess abgeschlossen ist. Sicher gibt es einen besseren Weg.

In Unix wird beim Verzweigen eines Prozesses ein Signalhandler aus dem übergeordneten Prozess heraus aufgerufen, wenn der untergeordnete Prozess abgeschlossen ist . Aber ich kann so etwas in Python nicht sehen. Vermisse ich etwas?

Wie kann das Ende eines untergeordneten Prozesses innerhalb des übergeordneten Prozesses beobachtet werden? (Natürlich möchte ich Process.join () nicht aufrufen, da dies die GUI-Oberfläche einfrieren würde.)

Diese Frage ist nicht auf die Mehrfachverarbeitung beschränkt: Ich habe genau das gleiche Problem mit der Mehrfachverarbeitung.

23
Matthew Walker

Diese Antwort ist wirklich einfach! (Ich habe nur Tage gebraucht, um das herauszufinden.)

In Kombination mit idle_add () von PyGTK können Sie einen AutoJoiningThread erstellen. Der Gesamtcode ist grenzwertig:

class AutoJoiningThread(threading.Thread):
    def run(self):
        threading.Thread.run(self)
        gobject.idle_add(self.join)

Wenn Sie mehr als nur beitreten möchten (z. B. Ergebnisse sammeln), können Sie die obige Klasse so erweitern, dass sie nach Abschluss Signale aussendet, wie im folgenden Beispiel dargestellt:

import threading
import time
import sys
import gobject
gobject.threads_init()

class Child:
    def __init__(self):
        self.result = None

    def play(self, count):
        print "Child starting to play."
        for i in range(count):
            print "Child playing."
            time.sleep(1)
        print "Child finished playing."
        self.result = 42

    def get_result(self, obj):
        print "The result was "+str(self.result)

class AutoJoiningThread(threading.Thread, gobject.GObject):
    __gsignals__ = {
        'finished': (gobject.SIGNAL_RUN_LAST,
                     gobject.TYPE_NONE,
                     ())
        }

    def __init__(self, *args, **kwargs):
        threading.Thread.__init__(self, *args, **kwargs)
        gobject.GObject.__init__(self)

    def run(self):
        threading.Thread.run(self)
        gobject.idle_add(self.join)
        gobject.idle_add(self.emit, 'finished')

    def join(self):
        threading.Thread.join(self)
        print "Called Thread.join()"

if __== '__main__':
    print "Creating child"
    child = Child()
    print "Creating thread"
    thread = AutoJoiningThread(target=child.play,
                               args=(3,))
    thread.connect('finished', child.get_result)
    print "Starting thread"
    thread.start()
    print "Running mainloop (Ctrl+C to exit)"
    mainloop = gobject.MainLoop()

    try:
        mainloop.run()
    except KeyboardInterrupt:
        print "Received KeyboardInterrupt.  Quiting."
        sys.exit()

    print "God knows how we got here.  Quiting."
    sys.exit()

Die Ausgabe des obigen Beispiels hängt von der Reihenfolge ab, in der die Threads ausgeführt werden. Sie ähnelt jedoch:

Kind erstellen 
 Thread erstellen 
 Thread starten 
 Kind beginnt zu spielen. 
 Kind spielt. 
 Mainloop ausführen (Strg + C zum Beenden) 
 Kind spielt. 
 Kind spielt. 
 Kind hat das Spiel beendet. 
 Thread.join () 
 Das Ergebnis war 42 
 ^ Erhalten KeyboardInterrupt. Beenden.

Es ist nicht möglich, einen AutoJoiningProcess auf die gleiche Weise zu erstellen (da wir idle_add () nicht für zwei verschiedene Prozesse aufrufen können). Wir können jedoch einen AutoJoiningThread verwenden, um das zu erhalten, was wir wollen:

class AutoJoiningProcess(multiprocessing.Process):
    def start(self):
        thread = AutoJoiningThread(target=self.start_process)
        thread.start() # automatically joins

    def start_process(self):
        multiprocessing.Process.start(self)
        self.join()

Ein weiteres Beispiel zur Demonstration von AutoJoiningProcess:

import threading
import multiprocessing
import time
import sys
import gobject
gobject.threads_init()

class Child:
    def __init__(self):
        self.result = multiprocessing.Manager().list()

    def play(self, count):
        print "Child starting to play."
        for i in range(count):
            print "Child playing."
            time.sleep(1)
    print "Child finished playing."
        self.result.append(42)

    def get_result(self, obj):
        print "The result was "+str(self.result)

class AutoJoiningThread(threading.Thread, gobject.GObject):
    __gsignals__ = {
        'finished': (gobject.SIGNAL_RUN_LAST,
                     gobject.TYPE_NONE,
                     ())
    }

    def __init__(self, *args, **kwargs):
        threading.Thread.__init__(self, *args, **kwargs)
        gobject.GObject.__init__(self)

    def run(self):
        threading.Thread.run(self)
        gobject.idle_add(self.join)
        gobject.idle_add(self.emit, 'finished')

    def join(self):
        threading.Thread.join(self)
        print "Called Thread.join()"

class AutoJoiningProcess(multiprocessing.Process, gobject.GObject):
    __gsignals__ = {
        'finished': (gobject.SIGNAL_RUN_LAST,
                     gobject.TYPE_NONE,
                     ())
        }

    def __init__(self, *args, **kwargs):
        multiprocessing.Process.__init__(self, *args, **kwargs)
        gobject.GObject.__init__(self)

    def start(self):
        thread = AutoJoiningThread(target=self.start_process)
        thread.start()

    def start_process(self):
        multiprocessing.Process.start(self)
        self.join()
        gobject.idle_add(self.emit, 'finished')

    def join(self):
        multiprocessing.Process.join(self)
        print "Called Process.join()"

if __== '__main__':
    print "Creating child"
    child = Child()
    print "Creating thread"
    process = AutoJoiningProcess(target=child.play,
                               args=(3,))
    process.connect('finished',child.get_result)
    print "Starting thread"
    process.start()
    print "Running mainloop (Ctrl+C to exit)"
    mainloop = gobject.MainLoop()

    try:
        mainloop.run()
    except KeyboardInterrupt:
        print "Received KeyboardInterrupt.  Quiting."
        sys.exit()

    print "God knows how we got here.  Quiting."
    sys.exit()

Die resultierende Ausgabe ist dem obigen Beispiel sehr ähnlich, außer dass diesmal sowohl der Prozess als auch der zugehörige Thread verbunden werden:

Kind erstellen 
 Thread erstellen 
 Thread starten 
 Mainloop ausführen (Strg + C zum Beenden) 
 Kind beginnt zu spielen. 
 Kind spielt. ____.] Kind spielt. 
 Kind spielt. 
 Kind hat das Spielen beendet. 
 Called Process.join () 
 Das Ergebnis war [42] 
 Aufgerufenes Thread.join () 
 ^ CReceived KeyboardInterrupt. Beenden.

Unglücklicherweise:

  1. Diese Lösung ist aufgrund der Verwendung von idle_add () von gobject abhängig. gobject wird von PyGTK verwendet.
  2. Dies ist keine echte Eltern-Kind-Beziehung. Wenn einer dieser Threads von einem anderen Thread gestartet wird, wird er dennoch von dem Thread verbunden, der den Mainloop ausführt, nicht von dem übergeordneten Thread. Dieses Problem gilt auch für AutoJoiningProcess, außer ich stelle mir vor, dass eine Ausnahme ausgelöst wird.

Um diesen Ansatz zu verwenden, ist es am besten, Threads/Prozesse nur innerhalb der Hauptschleife/GUI zu erstellen.

2
Matthew Walker

Ich denke, um Python plattformübergreifend zu machen, müssen einfache Dinge wie SIGCHLD selbst erledigt werden. Einverstanden, das ist ein bisschen mehr Arbeit, wenn Sie nur wissen möchten, wann das Kind fertig ist, aber es ist wirklich nicht so schmerzhaft. Berücksichtigen Sie Folgendes, das einen untergeordneten Prozess zum Ausführen der Arbeit, zwei Multiprocessing.Event-Instanzen und einen Thread zum Überprüfen verwendet, ob der untergeordnete Prozess abgeschlossen ist:

import threading
from multiprocessing import Process, Event
from time import sleep

def childsPlay(event):
    print "Child started"
    for i in range(3):
        print "Child is playing..."
        sleep(1)
    print "Child done"
    event.set()

def checkChild(event, killEvent):
    event.wait()
    print "Child checked, and is done playing"
    if raw_input("Do again? y/n:") == "y":
        event.clear()
        t = threading.Thread(target=checkChild, args=(event, killEvent))
        t.start()
        p = Process(target=childsPlay, args=(event,))
        p.start()
    else:
        cleanChild()
        killEvent.set()

def cleanChild():
    print "Cleaning up the child..."

if __== '__main__':
    event = Event()
    killEvent = Event()

    # process to do work
    p = Process(target=childsPlay, args=(event,))
    p.start()

    # thread to check on child process
    t = threading.Thread(target=checkChild, args=(event, killEvent))
    t.start()

    try:
        while not killEvent.is_set():
            print "GUI running..."
            sleep(1)
    except KeyboardInterrupt:
        print "Quitting..."
        exit(0)
    finally:
        print "Main done"

BEARBEITEN

Das Verknüpfen mit allen erstellten Prozessen und Threads ist eine gute Vorgehensweise, da hiermit angezeigt wird, wann Zombie-Prozesse/Threads (die nicht abgeschlossen werden) erstellt werden. Ich habe den obigen Code geändert und eine ChildChecker-Klasse erstellt, die von threading.Thread erbt. Der einzige Zweck besteht darin, einen Job in einem separaten Prozess zu starten, zu warten, bis dieser Prozess abgeschlossen ist, und dann die GUI zu benachrichtigen, wenn alles abgeschlossen ist. Wenn Sie am ChildChecker teilnehmen, wird auch der Prozess mitgemacht, den es "überprüft". Wenn der Prozess nach 5 Sekunden nicht verbunden wird, wird der Thread den Prozess beenden. Mit der Eingabe von "y" wird ein untergeordneter Prozess gestartet, in dem "endlessChildsPlay" ausgeführt wird, bei dem die Beendigung erzwungen werden muss.

import threading
from multiprocessing import Process, Event
from time import sleep

def childsPlay(event):
    print "Child started"
    for i in range(3):
        print "Child is playing..."
        sleep(1)
    print "Child done"
    event.set()

def endlessChildsPlay(event):
    print "Endless child started"
    while True:
        print "Endless child is playing..."
        sleep(1)
        event.set()
    print "Endless child done"

class ChildChecker(threading.Thread):
    def __init__(self, killEvent):
        super(ChildChecker, self).__init__()
        self.killEvent = killEvent
        self.event = Event()
        self.process = Process(target=childsPlay, args=(self.event,))

    def run(self):
        self.process.start()

        while not self.killEvent.is_set():
            self.event.wait()
            print "Child checked, and is done playing"
            if raw_input("Do again? y/n:") == "y":
                self.event.clear()
                self.process = Process(target=endlessChildsPlay, args=(self.event,))
                self.process.start()
            else:
                self.cleanChild()
                self.killEvent.set()

    def join(self):
        print "Joining child process"
        # Timeout on 5 seconds
        self.process.join(5)

        if self.process.is_alive():
            print "Child did not join!  Killing.."
            self.process.terminate()
        print "Joining ChildChecker thread"
        super(ChildChecker, self).join()


    def cleanChild(self):
        print "Cleaning up the child..."

if __== '__main__':
    killEvent = Event()
    # thread to check on child process
    t = ChildChecker(killEvent)
    t.start()

    try:
        while not killEvent.is_set():
            print "GUI running..."
            sleep(1)
    except KeyboardInterrupt:
        print "Quitting..."
        exit(0)
    finally:
        t.join()
        print "Main done"
11
manifest

Bei meinen Bemühungen, eine Antwort auf meine eigene Frage zu finden, stieß ich auf PyGTKs Funktion idle_add () . Das gibt mir folgende Möglichkeit:

  1. Erstellen Sie einen neuen untergeordneten Prozess, der über eine Warteschlange kommuniziert.
  2. Erstellen Sie einen Listener-Thread, der die Warteschlange abhört. Wenn der untergeordnete Prozess dem Listener eine Meldung sendet, dass er fertig ist, ruft der Listener idle_add () auf, mit dem ein Rückruf eingerichtet wird.
  3. Während der nächsten Zeit in der Hauptschleife ruft der übergeordnete Prozess den Rückruf auf.
  4. Der Rückruf kann Ergebnisse extrahieren, dem untergeordneten Prozess beitreten und dem Listener-Thread beitreten.

Dies scheint eine zu komplexe Methode zu sein, um den Rückruf unter Unix neu zu erstellen, wenn der untergeordnete Prozess abgeschlossen ist.

Dies muss ein häufig auftretendes Problem bei GUIs in Python sein. Sicher gibt es ein Standardmuster, um dieses Problem zu lösen?

2
Matthew Walker

Sie können ein queue verwenden, um mit untergeordneten Prozessen zu kommunizieren. Sie können Zwischenergebnisse oder Meldungen, die darauf hinweisen, dass Meilensteine ​​erreicht wurden (für Fortschrittsbalken), oder nur eine Meldung, die darauf hinweist, dass der Prozess zum Beitritt bereit ist. Das Abrufen mit leer ist einfach und schnell.

Wenn Sie wirklich nur wissen möchten, ob es fertig ist, können Sie den Exitcode Ihres Prozesses oder die Umfrage is_alive () ansehen.

2
nmichaels

schauen Sie sich das Subprozessmodul an:

http://docs.python.org/library/subprocess.html

import subprocess
let pipe = subprocess.Popen("ls -l", stdout=subprocess.PIPE)
allText = pipe.stdout.read()
pipe.wait()
retVal = pipe.returncode
0
Andy Skirrow