on button clicked multiprocessing threading

Programmierung für GNOME und GTK+, GUI-Erstellung mit Glade.
Antworten
Benutzeravatar
martinjo
User
Beiträge: 186
Registriert: Dienstag 14. Juni 2011, 20:03

Hallo

Ich arbeite mit Python 2.7 und Gtk3

Hier möchte ich gerne eine Funktion über einen Gtk.Button aufrufen. Diese soll dann im Hintergrund laufen. Ich habe es schon mal zum laufen bekommen aber inzwischen leider nicht mehr. Es wird bei beiden Beispielen die gesamte Anwendung blockiert.

Code: Alles auswählen

from threading import Thread
from multiprocessing import Process 


# Beispiel 1 -  threading
button.connect("clicked", self.on_run_background_process_clicked)
def on_run_background_process_clicked(slelf, widget):
        Thread(target=Shipping().add_shipping_numbers()).start()


# Beispiel 2 - multiprocessing
button.connect("clicked", self.on_run_background_process_clicked)
def on_run_background_process_clicked(slelf, widget):
    a = Process(name='add_shipping_numbers', target=Shipping().add_shipping_numbers())
    a.start()
    a.join()
Es wäre noch super eine Möglichkeit zu haben, zu prüfen ob ein Prozess schon läuft, denn 2x sollte der natürlich nicht laufen um Probleme mit Überschneidung zu vermeiden.
Zuletzt geändert von Anonymous am Dienstag 13. Juni 2017, 16:16, insgesamt 1-mal geändert.
Grund: Quelltext in Python-Codebox-Tags gesetzt.
BlackJack

@martinjo: Wenn ich mal raten müsste, dann ist gibt die `add_shipping_numbers()`-Methode *nicht* die Methode oder Funktion zurück die ausgeführt werden soll, sondern sie *ist* die Methode die ausgeführt werden soll‽ Dann darfst Du sie nicht aufrufen und den Rückgabewert an `Thread()` oder `Process()` übergeben, sondern die Methode selbst. Aufgerufen wird sie dann ja von `Thread`/`Process`.
Benutzeravatar
martinjo
User
Beiträge: 186
Registriert: Dienstag 14. Juni 2011, 20:03

Vielen Dank, Problem gelöst :-)

Eine Frage noch, kann ich auch verhindern, dass ein Prozess mehrmals parallel läuft? Ich kann ja sowohl "Thread" als auch "Process" einen Namen geben, kann man nicht anhand von diesem prüfen ob der Prozess bereits läuft?
BlackJack

@martinjo: Ich würde mir das einfach merken, denn normalerweise deaktiviert man bei solchen Sachen ja auch den Startbutton, damit der Benutzer das gar nicht noch einmal starten *kann* solange es läuft.
Benutzeravatar
martinjo
User
Beiträge: 186
Registriert: Dienstag 14. Juni 2011, 20:03

Hallo, da habe ich doch dann das selbe Problem, dass ich feststellen muss wann ein Prozess zu Ende ist um den Knopf wieder frei zu geben.
BlackJack

@martinjo: Über das Ende informiert doch hoffentlich der Prozess. :-) Ich würde ja `concurrent.futures` verwenden. Da kann man leicht zwischen Thread und Prozess wählen, und eine Rückruffunktion registrieren wenn die Aufgabe durch ist, egal ob wegen Ergebnis/normales Ende oder wegen Ausnahme.
__deets__
User
Beiträge: 14494
Registriert: Mittwoch 14. Oktober 2015, 14:29

@BlackJack: ist diese Rueckruffunktion nicht in einem extra Thread? Wenn ja, muss die natuerlich einen geeigneten Weg nutzen mit der GUI zu reden.
BlackJack

@__deets__: Jup, das muss man berücksichtigen.
Benutzeravatar
martinjo
User
Beiträge: 186
Registriert: Dienstag 14. Juni 2011, 20:03

Hallo
Ich hab es leider nicht ganz verstanden. Der Prozess informiert mich nicht wenn er zu ende ist.
BlackJack

@martinjo: Sollte er aber. :-)
Benutzeravatar
martinjo
User
Beiträge: 186
Registriert: Dienstag 14. Juni 2011, 20:03

In wie fern meldet er sich denn zurück?

Ich starte einen Prozess, der läuft ja im Hintergrund. Wie z.B. unten das Beispiel mit Process aber ohne join dann.

Ich kann jedoch prüfen ob der Prozess läuft mit p.is_alive()

Meine Lösung inzwischen ist nicht schön, trotzdem möchte ich Sie zumindest vorstellen:

Code: Alles auswählen

# Beispiel - multiprocessing
button.connect("clicked", self.on_run_background_process_clicked)

def on_run_background_process_clicked(slelf, widget):
        try:
            if self.p.is_alive():
                logger.warning("process is already running")
                return
            else:
                raise
        except:
                pass
    self.p = Process(name='add_shipping_numbers', target=Shipping().add_shipping_numbers())
    self.p.start()
Zuletzt geändert von Anonymous am Dienstag 20. Juni 2017, 21:04, insgesamt 1-mal geändert.
Grund: Quelltext in Python-Codebox-Tags gesetzt.
__deets__
User
Beiträge: 14494
Registriert: Mittwoch 14. Oktober 2015, 14:29

Bits du sicher, das das geht? Mir sieht das so aus als ob du die add_shipping_numbers gleich aufrufst - sind ja Klammern hinter. Das wurde ja schon besprochen...

Und statt es zu machen wie du wäre ein timer der is_alive prüft & dann den button enabled besser. Noch besser das von BJ angesprochene concurrent.futures.
BlackJack

@martinjo: Das war wohl missveständlich von mir: Mit sollte er aber meinte ich nicht das er das schon machen sollte, sondern das er das besser machen sollte, also Du das so programmieren solltest das er das macht. Das wäre nämlich die benutzerfreundliche Variante. Also zum Beispiel ein `concurrent.futures.Future` das am Ende über eine Rückruffunktion informiert das das Ergebnis da ist. Auch wenn der Prozess kein Ergebnis hat, ist die Information das es *da* ist, ja auch eins. Ausserdem kann man da auch erfahren ob die asynchrone Funktion tatsächlich durchgelaufen ist, oder ob sie mit einer Ausnahme endete. Die Information kann ja auch nützlich sein.

``raise`` ohne Argument? Das geht nicht. Also das geht schon — es löst einen `TypeError` aus der sagt das ``raise`` ohne Argument (also implizitem `None`) nicht geht. :-D Deswegen ist ja wahrscheinlich auch das unsinnige und unsägliche ``try``/``except`` um diesen Test. Ich hoffe mal das ist da nicht nur deswegen weil das `p`-Attribut vor dem ersten Start `None` ist und man darauf kein `is_alive()` aufrufen kann. *Das* kann man ja schliesslich einfach prüfen.

Code: Alles auswählen

    def on_run_background_process_clicked(self, widget):
        if self.process and self.process.is_alive():
            logger.warning('process is already running')
        else:
            self.process = Process(target=Shipping().add_shipping_numbers)
            self.process.start()
Benutzeravatar
martinjo
User
Beiträge: 186
Registriert: Dienstag 14. Juni 2011, 20:03

Hallo

Ja, die Klammern müssen weg, versehentlich aus dem ersten Beispiel übernommen.

@BlackJack
self.process existiert ja erst nachdem die Funktion das erste mal aufgerufen wurde. Da das Programm aber viele Funktionen hat möchte ich ungern self.process bereits beim initialisieren setzen.
BlackJack

@martinjo: Das ist ein guter Weg sich ins Knie zu schiessen — Klassen mit vielen Methoden die jeweils ihre eigenen Attribute erzeugen können. Nach der Abarbeitung von `__init__()` sollte eine Klasse alle Attribute besitzen und in einem benutzbaren Zustand sein. Das Du die Klasse Programm nennst und die Methode Funktion, und das es davon viele gibt, legt den Verdacht nahe das Du da eigentlich einen Haufen Funktionen mit ``global`` in eine Gott-Klasse verschoben hast. Dadurch verschwindet zwar das ``global``-Schlüsselwort, aber das eigentliche, zugrunde liegende Problem damit bleibt bestehen.
Antworten