Ende von Thread - Folgeablauf

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
Nobuddy
User
Beiträge: 994
Registriert: Montag 30. Januar 2012, 16:38

Hallo zusammen

Habe ein Problem und hoffe auf Eure Hilfe.

Ich habe eine Funktion, die Aufgaben über ein Terminal ausgeben lässt.

Code: Alles auswählen

def terminal(modul):
    """Vorbereitung für Terminalausgabe"""
    app = App()  # redirect stdout auf Terminal-Fenster
    worker = threading.Thread(target=modul)
    worker.start()  # macht die Arbeit in Hintergrund-Thread
    app.worker = worker
    app.run()  # aktiviert das Terminal-Fenster
    return
Ich nutze diese Funktion z.B. so:

Code: Alles auswählen

            if sub_button_name == 'Backup':
                from update_backup import backup_work
                print('00')
                terminal(backup_work)
                print('1')
                self.LoadNewLists()
                return
Mit z.B. 'terminal(backup_work)' starte ich das Terminal, in dem dann z.B. 'backup_work' abläuft.
Wenn dies dann fertig ist und ich das Terminal schließe, sollte noch die Funktion 'self.LoadNewLists()' abgearbeitet werden.
Zum Testen, habe ich die anschließende Print-Anweisung 'print('1')' platziert, um zu sehen ob der weitere Ablauf funktioniert, was es aber nicht tut. print('1') wird nicht ausgegeben und somit kann auch der weitere Ablauf nicht durchgearbeitet werden.

Wie kann ich dieses Problem lösen?

Grüße Nobuddy
BlackJack

@Nobuddy: Wenn ich mal raten müsste, dann blockiert `app.run()` solange bis, naja keine Ahnung, das hängt halt davon ab was `app.run()` eigentlich macht.
Nobuddy
User
Beiträge: 994
Registriert: Montag 30. Januar 2012, 16:38

Was ich noch vergessen hatte, ist daß nach Beendigung des Terminals, ich folgende Ausgabe erhalte:
invalid command name "643223920callit"
while executing
"643223920callit"
("after" script)
Nobuddy
User
Beiträge: 994
Registriert: Montag 30. Januar 2012, 16:38

Hallo BlackJack

Sorry, hatte zuerst das Falsche gepostet.
Hier ist das Modul gui_terminal.py:

Code: Alles auswählen

#!/usr/bin/env python
# coding: UTF-8

import sys
import tkinter.messagebox

try:
    #~~ For Python 2.x
    import Queue as qu
    import Tkinter as tk
except ImportError:
    #~~ For Python 3.x
    import queue as qu
    import tkinter as tk


class TeeStd(object):
    def __init__(self, queue, type):
       
        self.queue = queue

        if type == 'stderr':
            self.std_obj = sys.stderr
            sys.stderr = self
        else:
            self.std_obj = sys.stdout
            sys.stdout = self
       
    def write(self, data):
        # Ausgabe auf Tk-Textbox
        self.queue.put(data)


APP_WIN_XPOS = 170
APP_WIN_YPOS = 70

class App(object):
   
    def __init__(self, worker=None):
        self.win = tk.Tk()
        self.win.geometry('+{0}+{1}'.format(APP_WIN_XPOS, APP_WIN_YPOS))
        self.win.protocol("WM_DELETE_WINDOW", self.close)

        self.worker = worker
        self.queue = qu.Queue()
        self.queue_modules = qu.Queue()
       
        self.text = tk.Text(self.win, width=120, height=20, highlightthickness=0,
            bd=0, bg='white', relief='sunken', padx=5, pady=5)
        self.text.pack()

        self.close_button = tk.Button(self.win, text='Fenster schließen',
            state='disabled', command=self.close)
        self.close_button.pack()
               
        self.win.title("My Stdout/Stderr Terminal")

        # Umleitung der Standard-Ausgabe auf meine Textbox
        TeeStd(self.queue, 'stderr')
        TeeStd(self.queue, 'stdout')
               
        self.sampler()
   
    def sampler(self):
   
        if not self.queue.empty():
            data = self.queue.get_nowait()
            # Please do the job
            self.text.insert('end', data)
            self.text.see('end')
            self.queue.task_done()  
       
        if self.worker:
            if not self.worker.is_alive():
                self.enable_close_button()

        self.win.after(50, self.sampler)
       
    def run(self):
        self.win.mainloop()
   
    def enable_close_button(self):
        self.close_button['state']="normal"
   
    def close(self):
        """Process before shutdown"""
        text = 'Möchten Sie das Terminal schließen?'
        if tkinter.messagebox.askokcancel('Info', text):
            self.win.destroy()
Die Importanweisung lautet:

Code: Alles auswählen

from gui_terminal import App
Welches dann hier aufgerufen wird:

Code: Alles auswählen

def terminal(modul):
    """Vorbereitung für Terminalausgabe"""
    app = App()  # redirect stdout auf Terminal-Fenster
    worker = threading.Thread(target=modul)
    worker.start()  # macht die Arbeit in Hintergrund-Thread
    app.worker = worker
    app.run()  # aktiviert das Terminal-Fenster
    return
Nobuddy
User
Beiträge: 994
Registriert: Montag 30. Januar 2012, 16:38

Ich habe im Modul gui_terminal.py, in der Funktion 'close()' vor 'destroy' dies hinzugefügt:

Code: Alles auswählen

self.win.quit()
Seit dem kommt die Fehlermeldung nicht mehr, aber der weitere Programmablauf nach Beendigen des Terminals besteht immer noch.
BlackJack

@Nobuddy: Die GUI blockiert nun mal und Tkinter sollte im Hauptthread laufen. Wenn Du neben dem Worker noch etwas anderes parallel haben möchtest, müsstest Du das in einen eigenen Thread auslagern. Übersichtlicher wird es dadurch allerdings nicht.
Nobuddy
User
Beiträge: 994
Registriert: Montag 30. Januar 2012, 16:38

Also das Modul 'gui_terminal.py' in mein Hauptmodul einbauen, ja?
BlackJack

@Nobuddy: Keine Ahnung was Du damit meinst.

Das `app.run()` nicht blockiert, möchtest Du übrigens auch gar nicht, denn danach würde die Funktion enden und `app` wäre nicht mehr erreichbar, womit das Objekt Freiwild für die Speicherbereinigung sein dürfte.
Nobuddy
User
Beiträge: 994
Registriert: Montag 30. Januar 2012, 16:38

Ich habe das Ganze jetzt komplett in das Modul 'gui_terminal.py ausgelagert.
Sieht jetzt so aus:

Code: Alles auswählen

#!/usr/bin/env python
# -*- coding: utf-8 -*-
# For Python3.x

import sys
import tkinter.messagebox

import threading
import queue as qu
import tkinter as tk


class TeeStd(object):
    def __init__(self, queue, type):
       
        self.queue = queue

        if type == 'stderr':
            self.std_obj = sys.stderr
            sys.stderr = self
        else:
            self.std_obj = sys.stdout
            sys.stdout = self
       
    def write(self, data):
        # Ausgabe auf Tk-Textbox
        self.queue.put(data)


APP_WIN_XPOS = 170
APP_WIN_YPOS = 70

class App(object):
   
    def __init__(self, modul):
        self.win = tk.Toplevel()
        self.win.geometry('+{0}+{1}'.format(APP_WIN_XPOS, APP_WIN_YPOS))
        self.win.protocol("WM_DELETE_WINDOW", self.close)

        self.worker = threading.Thread(target=modul)
        self.queue = qu.Queue()
        self.queue_modules = qu.Queue()
       
        self.text = tk.Text(self.win, width=120, height=20,
            highlightthickness=0, bd=0, bg='white', relief='sunken',
            padx=5, pady=5)
        self.text.pack()

        self.close_button = tk.Button(self.win, text='Fenster schließen',
            state='disabled', command=self.close)
        self.close_button.pack()
               
        self.win.title("My Stdout/Stderr Terminal")

        # Umleitung der Standard-Ausgabe auf meine Textbox
        TeeStd(self.queue, 'stderr')
        TeeStd(self.queue, 'stdout')

        self.worker.start()
        self.sampler()
   
    def sampler(self):
        if not self.queue.empty():
            data = self.queue.get_nowait()
            # Please do the job
            self.text.insert('end', data)
            self.text.see('end')
            self.queue.task_done()
       
        if self.worker:
            if not self.worker.is_alive():
                self.enable_close_button()

        self.win.after(50, self.sampler)
       
    def run(self):
        self.win.mainloop()
   
    def enable_close_button(self):
        self.close_button['state']="normal"
   
    def close(self):
        """Process before shutdown"""
        text = 'Möchten Sie das Terminal schließen?'
        if tkinter.messagebox.askokcancel('Info', text):
            self.win.quit()
            self.win.destroy()
Der Aufruf:

Code: Alles auswählen

App(backup_work).run()
Vielleicht könntest Du da mal drüber schauen, ob Dir ein Fehler auffällt?
Nobuddy
User
Beiträge: 994
Registriert: Montag 30. Januar 2012, 16:38

Nachdem ich wie oben abgeändert habe, funktioniert es wie bisher.

Was mich irritiert, ist folgendes.
Wenn ich das Modul so aufrufe:

Code: Alles auswählen

            if sub_button_name == 'Backup':
                from update_backup import backup_work
                infoneu()
                print('00')
                App(backup_work).run()
                print('01')
                return
Wird 'print('00')' ausgegeben aber 'print('01')' nicht.
Sobald ich das Terminal beende, schließt es auch sofort.


Wenn ich das Modul aber so aufrufe:

Code: Alles auswählen

            if sub_button_name == 'Backup':
                from update_backup import backup_work
                infoneu()
                print('00')
                App(backup_work).run()
                print('01')
                self.LoadNewLists()
                return
Wird 'print('00')' ausgegeben aber 'print('01')' nicht.
Sobald ich das Terminal beende, dauert es eine ganze Weile, bis es sich schließt.
Hier vermute ich das die danach aufgerufene Funktion 'self.LoadNewLists()' ausgeführt wird.
Wenn das so wäre, warum aber nicht die letzte Print-Anweisung?
BlackJack

@Nobuddy: Vielleicht wird das ``print`` ja ausgegeben aber Du siehst es nicht weil das Fenster beendet ist, also die GUI-Hauptschleife nicht mehr läuft, aber Du ja die Standardausgabe auf das Fenster gelegt hast.

Der ganze Entwurf sieht komisch aus und das mit der Ausgabeumleitung würde ich als unschönen Hack bezeichnen. Statt die Standardausgabe zu kapern sollte man einfach keine ``print``\s für etwas nehmen was nicht auf der Konsole landen soll. Oder mindestens dafür sorgen, dass die Standardausgaben auch wiederhergestellt werden, wenn die GUI das nicht mehr anzeigen kann.
Nobuddy
User
Beiträge: 994
Registriert: Montag 30. Januar 2012, 16:38

Hallo BlackJack

Ich arbeite mit Geany, dort kann ich über das Terminal von Geany den Ablauf überwachen, was gerade beim Testen sehr hilfreich ist.
Daher bin ich davon ausgegangen, daß nach Beendigung von 'App(backup_work).run()', die Print-Anweisung im Geany-Terminal ausgegeben wird.
Wie ich aber jetzt festgestellt habe, wenn auch das mit der Print-Anweisung (Test zur Kontrolle) nicht funktioniert, so werden die danach kommenden Funktionen abgearbeitet und das ist gut so. :wink:

Für manche Aufgaben benötige ich einfach eine visuelle Kontrolle über den Ablauf, daher auch die Ausgabe auf dem Terminal.

Der Entwurf von gui_terminal.py lässt sich bestimmt noch schöner und effektiver gestalten.

Wie meinst Du das mit:
BlackJack hat geschrieben:Oder mindestens dafür sorgen, dass die Standardausgaben auch wiederhergestellt werden, wenn die GUI das nicht mehr anzeigen kann.
Kannst Dur mir da den richtigen Schubser in die richtige Richtung geben, evtl. ein Beispiel?
EyDu
User
Beiträge: 4881
Registriert: Donnerstag 20. Juli 2006, 23:06
Wohnort: Berlin

Nobuddy hat geschrieben:Daher bin ich davon ausgegangen, daß nach Beendigung von 'App(backup_work).run()', die Print-Anweisung im Geany-Terminal ausgegeben wird.
Wie kommst du auf die Idee? Wenn du deiner Anwendung nicht sagst, dass wieder auf der Standardausgabe geschrieben werden soll, woher soll sie es dann wissen?
Nobuddy hat geschrieben:Wie meinst Du das mit:
BlackJack hat geschrieben:Oder mindestens dafür sorgen, dass die Standardausgaben auch wiederhergestellt werden, wenn die GUI das nicht mehr anzeigen kann.
Kannst Dur mir da den richtigen Schubser in die richtige Richtung geben, evtl. ein Beispiel?
Dass du einfach die Ausgaben wieder auf die alten Ziele umbiegst.
Das Leben ist wie ein Tennisball.
Nobuddy
User
Beiträge: 994
Registriert: Montag 30. Januar 2012, 16:38

Hallo EyDu

Habe das jetzt verstanden, aber wie mache ich das "auf die alten Ziele umbiegen"?
EyDu
User
Beiträge: 4881
Registriert: Donnerstag 20. Juli 2006, 23:06
Wohnort: Berlin

Du speicherst doch schon die ursprünglichen Werte von stdout und stderr in std_obj, dabei hast du dir doch sicher etwas gedacht ;-)
Das Leben ist wie ein Tennisball.
Nobuddy
User
Beiträge: 994
Registriert: Montag 30. Januar 2012, 16:38

Ja hier:

Code: Alles auswählen

class TeeStd(object):
    def __init__(self, queue, type):
       
        self.queue = queue

        if type == 'stderr':
            self.std_obj = sys.stderr
            sys.stderr = self
        else:
            self.std_obj = sys.stdout
            sys.stdout = self
Meinst Du so etwas in der Art:

Code: Alles auswählen

    def close(self):
        """Process before shutdown"""
        text = 'Möchten Sie das Terminal schließen?'
        if tkinter.messagebox.askokcancel('Info', text):
            sys.stdout = self.std_obj
            self = sys.stdout
            sys.stderr = self.std_obj
            self = sys.stderr
            self.win.quit()
            self.win.destroy()
        return
Nobuddy
User
Beiträge: 994
Registriert: Montag 30. Januar 2012, 16:38

Ich bin selbst drauf gekommen.

Code: Alles auswählen

#!/usr/bin/env python
# -*- coding: utf-8 -*-
# For Python3.x

import sys
import tkinter.messagebox

import threading
import queue as qu
import tkinter as tk


class TeeStd(object):
    def __init__(self, queue, type):
       
        self.queue = queue

        if type == 'stderr':
            self.std_obj = sys.stderr
            sys.stderr = self
        else:
            self.std_obj = sys.stdout
            sys.stdout = self
       
    def write(self, data):
        # Ausgabe auf Tk-Textbox
        self.queue.put(data)


APP_WIN_XPOS = 170
APP_WIN_YPOS = 70

class App(object):
   
    def __init__(self, modul):
        self.win = tk.Toplevel()
        self.win.geometry('+{0}+{1}'.format(APP_WIN_XPOS, APP_WIN_YPOS))
        self.win.protocol("WM_DELETE_WINDOW", self.close)

        self.worker = threading.Thread(target=modul)
        self.queue = qu.Queue()
        self.queue_modules = qu.Queue()
       
        self.text = tk.Text(self.win, width=120, height=20,
            highlightthickness=0, bd=0, bg='white', relief='sunken',
            padx=5, pady=5)
        self.text.pack()

        self.close_button = tk.Button(self.win, text='Fenster schließen',
            state='disabled', command=self.close)
        self.close_button.pack()
               
        self.win.title("My Stdout/Stderr Terminal")

        # Sicherung der Standard-Ausgabe
        self.save_stdout = sys.stdout
        self.save_stderr = sys.stderr

        # Umleitung der Standard-Ausgabe auf meine Textbox
        TeeStd(self.queue, 'stderr')
        TeeStd(self.queue, 'stdout')

        self.worker.start()
        self.sampler()
   
    def sampler(self):
        if not self.queue.empty():
            data = self.queue.get_nowait()
            # Please do the job
            self.text.insert('end', data)
            self.text.see('end')
            self.queue.task_done()
       
        if self.worker:
            if not self.worker.is_alive():
                self.enable_close_button()

        self.win.after(50, self.sampler)
       
    def run(self):
        self.win.mainloop()
   
    def enable_close_button(self):
        self.close_button['state']="normal"
   
    def close(self):
        """Process before shutdown"""
        # Umleitung auf die Standard-Ausgabe
        sys.stdout = self.save_stdout
        sys.stderr = self.save_stderr
        text = 'Möchten Sie das Terminal schließen?'
        if tkinter.messagebox.askokcancel('Info', text):
            self.win.quit()
            self.win.destroy()
        return
Antworten