Wie Unterprozesse nacheinander starten?

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
Benutzeravatar
atarax
User
Beiträge: 44
Registriert: Samstag 14. Juni 2008, 22:49
Wohnort: Berlin
Kontaktdaten:

Dieses Skript soll nacheinander zwei Stücke einer Audio-CD rippen. Bei einem einzelnen Stück funktioniert es auch, aber bei mehr als einem Stück werden die Unterprozesse schon gestartet, bevor der jeweils vorherige Prozess abgeschlossen ist, was dazu führt, dass der Lesekopf vom CD-Player natürlich immer hin- und her springt, da er versucht, mehrere Stücke gleichzeitig zu rippen. Wie kann ich das Skript so ändern, dass der nächste Prozess immer erst dann gestartet wird, wenn der vorherige abgeschlossen ist? Vielen Dank!

Code: Alles auswählen

    def button2_clicked(self, widget):
        for self.trackNumber in range(11,13):  # main rip loop
            self.ripTrack(self.trackNumber)
            
    def ripTrack(self, n):  # rip selected track to wav file
        task = self.ripParanoia(n)
        gobject.idle_add(task.next)
        
    def ripParanoia(self, n):
        cmd = ['cdparanoia', '-e', '-v', '-Z', '-B', '-d', '/dev/scd0', str(n), '/home/atarax/Desktop/Rip/ripper03/wav']
        ripping = subprocess.Popen(cmd, stderr=subprocess.PIPE)
        for line in ripping.stderr:
            if "[read]" in line:
                position = line[(line.rfind(" ") + 1):-1]
                self.entry1.set_text(position)
            yield True
        yield False
http://www.decocode.de/
BlackJack

@atarax: Ich würde die Schaltfläche nach dem Klick deaktivieren und erst am Ende des Ripvorganges wieder aktivieren. Also wohl dann, wenn die ``for``-Schleife dort abgearbeitet ist.
Benutzeravatar
atarax
User
Beiträge: 44
Registriert: Samstag 14. Juni 2008, 22:49
Wohnort: Berlin
Kontaktdaten:

Ja, das ist schon realisiert. Aber das Problem ist ja nicht, dass während des Ripvorgangs erneut der Button geklickt wird, sondern, dass - wie üblich - vor dem Auslösen des Buttons in der Titelliste die gewünschten Titel ausgewählt werden und dann in der Schleife abgearbeitet werden. Wenn ich bspw. fünf Titel auswähle, werden eben auch fünf Prozesse gestartet, es darf aber immer nur einen geben. Irgendwo müsste eine Kontrolle eingebaut werden, ob der aktuelle Prozess noch läuft oder bereits beendet wurde, und dass erst dann die Schleife fortgesetzt wird. Mir ist aber nicht klar, wie das gemacht werden muss. Ich möchte hier nicht so gerne rumexperimentieren, weil ich fürchte, mir sonst mein Laufwerk zu beschädigen...
http://www.decocode.de/
Benutzeravatar
Hyperion
Moderator
Beiträge: 7478
Registriert: Freitag 4. August 2006, 14:56
Wohnort: Hamburg
Kontaktdaten:

Was macht denn folgende Zeile:

Code: Alles auswählen

gobject.idle_add(task.next)
Das wird ja irgend ein Scheduler sein, der die Prozesse abarbeiten soll.

Das Problem dürfte sein, dass das Auslesen ja bereits gestartet wird, wenn Du ripParanoia() aufrufst!
encoding_kapiert = all(verstehen(lesen(info)) for info in (Leonidas Folien, Blog, Folien & Text inkl. Python3, utf-8 everywhere))
assert encoding_kapiert
Benutzeravatar
atarax
User
Beiträge: 44
Registriert: Samstag 14. Juni 2008, 22:49
Wohnort: Berlin
Kontaktdaten:

@Hyperion:
Die Zeile kommt von hier: How do I update a progress bar and do some work at the same time
Diese Funktion wird hier beschrieben: gobject.idle_add
Ich hab das einfach in mein Skript eingebaut, aber was da genau passiert, weiß ich auch nicht.
http://www.decocode.de/
Benutzeravatar
Hyperion
Moderator
Beiträge: 7478
Registriert: Freitag 4. August 2006, 14:56
Wohnort: Hamburg
Kontaktdaten:

Dann liegt das Problem doch auf der Hand: Du musst einfach unterbinden, dass mehrere Tasks parallel gestartet werden können. Wie Du das genau realisieren kannst, weiß ich nicht. Das beste wäre, wenn Du hier

Code: Alles auswählen

gobject.idle_add(task.next)
irgend wie an einen Rückgabewert kämest oder irgend eine Möglichkeit hast damit festzustellen, dass das "False" "geyielded" wurde.
encoding_kapiert = all(verstehen(lesen(info)) for info in (Leonidas Folien, Blog, Folien & Text inkl. Python3, utf-8 everywhere))
assert encoding_kapiert
Benutzeravatar
atarax
User
Beiträge: 44
Registriert: Samstag 14. Juni 2008, 22:49
Wohnort: Berlin
Kontaktdaten:

Das sehe ich auch so. Ich verstehe den Ablauf momentan so, dass der nächste Prozess erst dann gestartet wird (gobject.idle_add(task.next)), wenn der Rückgabewert (yield) von ripParanoia() False ist, denn bis dahin ist der Prozess ja eben gerade nicht idle. Aber genau das passiert eben nicht.
http://www.decocode.de/
Benutzeravatar
Hyperion
Moderator
Beiträge: 7478
Registriert: Freitag 4. August 2006, 14:56
Wohnort: Hamburg
Kontaktdaten:

Nein, nein, das klappt schon so! Das Problem liegt quasi hier:

Code: Alles auswählen

def ripTrack(self, n):
    # Dieser Aufruf passiert doch quasi mehrfach direkt hintereinander
    task = self.ripParanoia(n)
    # hier ist es schon zu spät
    gobject.idle_add(task.next)
Genau da liegt das Problem. Sobald Du die Funktion ripParanoia aufrufst, stößt Du doch schon einen neuen Ripping-Process extern an. Das darf erst dann passieren, wenn dieser gobject-Scheduler fertig ist mit dem letzten.
encoding_kapiert = all(verstehen(lesen(info)) for info in (Leonidas Folien, Blog, Folien & Text inkl. Python3, utf-8 everywhere))
assert encoding_kapiert
Benutzeravatar
atarax
User
Beiträge: 44
Registriert: Samstag 14. Juni 2008, 22:49
Wohnort: Berlin
Kontaktdaten:

Also ich vermute, dass das Problem eher in der for-Schleife zu suchen ist, die ja im Moment brav durchlaufen wird, ohne eben zu prüfen, wie viele Prozesse schon laufen. Vielleicht gibt es eine Möglichkeit, den Inkrement der Schleife an eine Bedingung zu koppeln, die genau diese Prüfung vornimmt...
http://www.decocode.de/
Benutzeravatar
Hyperion
Moderator
Beiträge: 7478
Registriert: Freitag 4. August 2006, 14:56
Wohnort: Hamburg
Kontaktdaten:

atarax hat geschrieben:Also ich vermute, dass das Problem eher in der for-Schleife zu suchen ist, die ja im Moment brav durchlaufen wird, ohne eben zu prüfen, wie viele Prozesse schon laufen. Vielleicht gibt es eine Möglichkeit, den Inkrement der Schleife an eine Bedingung zu koppeln, die genau diese Prüfung vornimmt...
Ähem... schau Dir doch mal die for-Schleife an... da passiert ja nix, als dass die gerade von mir kommentierte ripTrack aufgerufen wird! Letztlich kann ich bis runter gehen bis in die ripParanoia(), da dort ja per subprocess der Ripper gecallt wird. Ehrlich gesagt dachte ich, das wäre jetzt klar gewesen :shock:
encoding_kapiert = all(verstehen(lesen(info)) for info in (Leonidas Folien, Blog, Folien & Text inkl. Python3, utf-8 everywhere))
assert encoding_kapiert
Benutzeravatar
atarax
User
Beiträge: 44
Registriert: Samstag 14. Juni 2008, 22:49
Wohnort: Berlin
Kontaktdaten:

Na das Problem wird so langsam in der Tat immer klarer. Nur eine Lösung erscheint mir noch nicht. Vielleicht statt einer for-Schleife eine while-Schleife mit einer zusätzlichen Bedingung? Wobei mir noch nicht klar ist, wie diese Bedingung zu formulieren ist...
http://www.decocode.de/
Benutzeravatar
Hyperion
Moderator
Beiträge: 7478
Registriert: Freitag 4. August 2006, 14:56
Wohnort: Hamburg
Kontaktdaten:

Das Problem ist klar:

Noch mal langsam: Du hast eine for-Schleife, in der Du unmittelbar nacheinander über eine Funktion hinweg den Subprozess anstößt. Ob Du da etwas in diese gobject Prozess-Queue reinpackst oder nicht, spielt dabei keine Rolle.

Meine quick and dirty Idee: Um zu verhindern, dass die ripParanoia()-Funktion vor dem Hinzuügen in die Queue aufgerufen wird, übergebe doch direkt den Callback und nicht schon den Iterator:

Code: Alles auswählen

    def button2_clicked(self, widget):
        # wieso iterierst Du eigentlich über eine ObjektVariable?
        for self.trackNumber in range(11,13):
            # einfach nur den Cllback übergeben und dann die tracknummer als weiteren Parameter.
            gobject.idle_add(self.ripParanoia, self.trackNumber)
Alternativ könntest Du auch versuchen, in der ripParanoia() vor dem Aufruf des Subprozesses ein "yield True" zu setzen. Aber das würde mir persönlich zu dirty sein!
encoding_kapiert = all(verstehen(lesen(info)) for info in (Leonidas Folien, Blog, Folien & Text inkl. Python3, utf-8 everywhere))
assert encoding_kapiert
Benutzeravatar
atarax
User
Beiträge: 44
Registriert: Samstag 14. Juni 2008, 22:49
Wohnort: Berlin
Kontaktdaten:

Na, ich hab das mal probiert. Außer dass ich eine 100%ige Prozessorlast bekomme (bis ich das Programm selbst beende), passiert nichts. Keine Tracks werden gerippt und self.entry1.set_text(position) funktioniert auch nicht... Zu dirty, wie es scheint :wink:
http://www.decocode.de/
Benutzeravatar
Hyperion
Moderator
Beiträge: 7478
Registriert: Freitag 4. August 2006, 14:56
Wohnort: Hamburg
Kontaktdaten:

Sei doch mal präziser! Ich habe doch zwei Vorschläge gemacht! Welchen hast Du denn probiert?
encoding_kapiert = all(verstehen(lesen(info)) for info in (Leonidas Folien, Blog, Folien & Text inkl. Python3, utf-8 everywhere))
assert encoding_kapiert
Benutzeravatar
atarax
User
Beiträge: 44
Registriert: Samstag 14. Juni 2008, 22:49
Wohnort: Berlin
Kontaktdaten:

Sorry, meine Antwort bezog sich auf deinen ersten Vorschlag. Ich habe jetzt noch den anderen probiert, aber da rumpelt es wieder in meinem CD-Laufwerk...
http://www.decocode.de/
Antworten