Autarke Ausführung von Funktionen im Hintergrund

Fragen zu Tkinter.
Antworten
stielchen
User
Beiträge: 14
Registriert: Sonntag 27. Juli 2014, 09:28

Hallo zusammen,
weiß evtl. jemand, wie man eine Python-Funktion eines Skriptes (also kein externes Skript!) als unabhängiges Programm starten kann, sodass dieses im Hintergrund weiterläuft, wenn das Hauptprogramm (aus dem es gestartet worden ist) beendet wurde?

Ich habe schon einige Sachen ausprobiert, aber leider war nicht so das richtige dabei:
- "subprocess.Popen" scheint nur für den Aufruf von eigenständigen externen Programmen geeignet zu sein, aber nicht für Funktionsaufrufe.
- Mit start_new_thread() gestartete Funktionen werden leider mit dem Hauptprogramm beendet.
- fork() scheint es leider nur auf UNIX-Systemen zu geben(?)
- Multiprocessing scheint ein Ersatz für die Threads zu sein und die GIL-Problematik zu umgeben, aber scheint für mein Vorhaben auch nicht zu passen.

Danke Euch für Tipps
stielchen
Sirius3
User
Beiträge: 17767
Registriert: Sonntag 21. Oktober 2012, 17:20

@stielchen: was ist denn Dein Vorhaben konkret? Eine Funktion zeichnet sich ja dadurch aus, dass sie im Kontext eines Programms ausgeführt wird und nicht unabhängig davon.
stielchen
User
Beiträge: 14
Registriert: Sonntag 27. Juli 2014, 09:28

Hi Sirius3,
ich möchte einen Socket-Listener implementieren, der wahlweise auf Windows oder Linux läuft und Befehle aus einer VirtualBox-VM empfängt und diese auf dem Host ausführt. Der Listener soll dabei die empfangenen Kommandos entweder in einem Tkinter-Log-Fenster protokollieren oder still im Hintergrund einfach die Befehle ausführen.

Klappt alles bereits wunderbar, zumindest mit der sichbaren Protokollierung, wenn ich mich aber als Anwender für die Ausführung im Hintergrund entscheide schieße ich mit der GUI via destroy() auch meinen Listener ab. Es scheint mir so, als ob die Existenz der mainloop() auch Einfluss auf alle anderen Prozesse hat. Hätte gedacht, dass man die grafische Oberfläche einfach abschalten kann (habe diese ja vorher mit dem Aufruf von mainloop() ja auch gestartet) und der Listener läuft dann weiter im Hintergrund.

Ich könnte zwar den Listener in ein weiteres Python-Skript auslagern und dann aufrufen, aber ich hätte das alles lieber in einem einzigen Skript verpackt.
BlackJack

@stielchen: Starte den Listener in einem Thread — und das bitte *nicht* mit dem `thread`-Modul das nun schon seit einer Eeewigkeit durch `threading` abgelöst wurde — und nach dem Ende der grafischen Oberfläche machst Du dann ein `join()` auf diesem Thread.
stielchen
User
Beiträge: 14
Registriert: Sonntag 27. Juli 2014, 09:28

@BlackJack:
Ich habe eben mal nachgelesen, aber so richtig verstanden habe ich es noch nicht. Wie müsste denn so ein Aufruf aussehen? So etwa?

Code: Alles auswählen

#!/usr/bin/python
# -*- coding: utf-8 -*-
from Tkinter import Tk, BOTH
from ttk import Frame, Button
import threading
        
def start_listener(listener_port):
    # :
    # Listener-Code
    # :
    pass
    
class Example(Frame):
    def __init__(self, parent):
        Frame.__init__(self, parent)   
        self.parent = parent
        self.initUI()

    def background(self):
        t = threading.Thread(target=start_listener, args=(50009,))
        t.start()
        t.join()  
                    
    def initUI(self):
        self.pack(fill=BOTH, expand=1)
        Button(self, text="Listener to background",
            command=self.background).place(x=50, y=50)  

def main():
    root = Tk()
    root.geometry("250x150+300+300")
    app = Example(root)
    root.mainloop()  

if __name__ == '__main__':
    main()  
Ich hatte bislang das Problem dass ich ja die mainloop() und den accept() vom Socket parallel laufen lassen musste und beide in einer Warteschleife endeten. Ich hatte es so gelöst, dass ich vor mainloop mit "after" den listener aufgerufen habe:

Code: Alles auswählen

main.after(0, <listener-funktion>)
main.mainloop()
Was mich doch irritiert ist, dass ich bislang davon ausgegangen bin, dass die mainloop() für die GUI-Behandlung zuständig ist und ich vermutete man kann diese einfach beenden und die Listener-Funktion läuft dann weiter.

Ist denn überhaupt mein Vorgehen die grafische Oberfläche mit "destroy()" zu beenden denn korrekt? Bislang habe ich damit nur Widgets gelöscht.
BlackJack

@stielchen: Warum denn jetzt `multiprocessing`? Und egal ob Du nun einen Thread startest oder einen Prozess musst Du das warten auf dessen Ende erst machen wenn die GUI geschlossen wurde.

Alles was von der GUI-Hauptschleife aus per Rückruffunktion ausgeführt wird, ob das nun per `after()` registriert wurde oder beim anklicken einer Schaltfäche aufgerufen wird darf *nicht* lange laufen, schon gar nicht ewig/so lange wie der Listener, denn dann kehrt der Programmfluss ja nicht mehr zur GUI-Hauptschleife zurück und die GUI reagiert nicht mehr und wird auch nicht mehr aktualisiert. Deine `background()`-Methode startet den Listener in einem Prozess und wartet dann gleich auf das Ende von dem Prozess, das macht keinen Sinn, denn solange gewartet wird kann die GUI nicht ”laufen”.

Die `initUI()` entspricht in der Namenschreibweise nicht dem Style Guide, und ist auch nicht wirklich sinnvoll. Die Klasse ist eine UI-Klasse und die Initialisierungsmethode heisst `__init__()`, also ist das die Init für die UI. Warum da noch mal zwei Zeilen in eine zusätzliche Methode verschieben?

`background()` ist so wie es dort steht auch keine Methode, das kann man auch problemlos als Funktion schreiben. Also sollte man entweder genau das tun, oder wenigstens mit `staticmethod()` als Dekorator kennzeichnen dass es Absicht ist die Funktion in die Klasse zu stecken. Andererseits könnte man da auch eine Methode draus machen die den Thread oder Process an das Objekt bindet und dann nachdem die GUI-Hauptschleife *beendet* ist, also die GUI geschlossen wurde, mit `join()` auf das Ende warten.

Zur GUI noch: Man sollte weder die Koordinaten und die Grösse des Fensters vorgeben noch `place()` benutzen. Die Koordinaten überlässt man besser der Fensterverwaltung, die weiss das entweder besser oder lässt sich vom Benutzer individuell nach seinen Wünschen konfigurieren. Und wie gross das Fenster werden muss ergibt sich automatisch aus dem was darin angezeigt wird. Also wenn man nicht das unsägliche `place()` benutzt. Und Wigets layouten sich nicht selbst! Das macht keines der bereits vorhandenen. Man nimmt damit dem Aufrufer die Möglichkeit zu entscheiden wie und wo ein Widget layoutet wird.
stielchen
User
Beiträge: 14
Registriert: Sonntag 27. Juli 2014, 09:28

@BlackJack:
Sorry, hatte mich vertan. Hatte ein Code-Schnippsel aus dem Web entnommen und angepasst. Hatte es aber noch korrigiert, als ich es bemerkte habe.

Aber wie kann man denn dann Sockets zusammen mit einer GUI verwenden, die accept-Methode wartet ja leider so lange bis was reinkommt und blockiert somit die GUI. Irgendwie scheinen die beiden Dinge nicht so recht miteinander kompatibel zu sein.

Meine Versuche mit join() laufen leider noch nicht...
BlackJack

@stielchen: Naja der Server muss halt in einem eigenen Thread laufen damit er die GUI nicht blockiert. Ich denke genau darum geht es hier die ganze Zeit‽
Antworten