Qt - Threads - Timers cannot be stopped from another thread

Python und das Qt-Toolkit, erstellen von GUIs mittels des Qt-Designers.
Antworten
bachatero18
User
Beiträge: 41
Registriert: Montag 12. August 2019, 13:57

Ich habe eine GUI mittel Qt erstellt und habe da probleme wenn ich diese als Thread öffne. Im prinzip ist die GUI nur ein searching Wheel was dem Benutzer sagt "Ok das programm läuft noch und ist nicht abgestürzt"

Also mein Ziel ist es:
- Caller.py - öffnet searchingWheel GUI und der caller.py macht dann seine restliche Arbeit die unter umständen mehrere minuten oder stunden dauert.
- Wenn Bei der GUI auf abbrechen gedrückt wird soll vorzeigt alles abgebrochen werden
- Wenn die Hauptarbeit beendet ist soll die GUI vom caller.py aus geschlossen werden.

Ist gerade erst im Aufbau deshalb hab ich das mal auf das wesentliche runtergebrochen.

Nun das Problem wenn ich auf Abbrechen drücke wenn ich die GUI aus caller.py öffne (wenn ich sie direkt öffne ist alles gut) bekomme ich diese Meldung "QObject::~QObject: Timers cannot be stopped from another thread" und ich weißt nicht wie ich das lösen soll oder was ich falsch mache.

Ich hoffe auf eure Hilfe
VG Bachatero18

searching_wheel.py

Code: Alles auswählen

import sys
from PySide2 import QtWidgets
from ui_searching_wheel import Ui_MainWindow


class MainWindow(QtWidgets.QMainWindow):

    def __init__(self, label_text, speed):
        super().__init__()
        self.ui = Ui_MainWindow()
        self.ui.setupUi(self)
        self.ui.cancelButton.clicked.connect(self.cancel_button_clicked)

    def cancel_button_clicked(self):
        self.timer.stop()
        self.close()  # Schließe das Fenster nach dem Ausführen der Aktionen


def main(label_text: str = "", speed: int = 5):
    app = QtWidgets.QApplication(sys.argv)
    window = MainWindow(label_text, speed)
    window.show()
    sys.exit(app.exec_())


if __name__ == "__main__":
    main()

caller.py

Code: Alles auswählen

import threading
import searching_wheel
import time


def one():
    searching_wheel.main("Beispiel", 35)


def two():
    for i in range(5):
        i += 1
        print("action")
        time.sleep(1)


gui_thread = threading.Thread(target=one)
gui_thread.start()

two()

gui_thread.join()

print('Alle Aufgaben wurden abgeschlossen.')
__deets__
User
Beiträge: 14544
Registriert: Mittwoch 14. Oktober 2015, 14:29

Man kann keine GUI im anderen Thread betreiben. Geht einfach nicht. Die Funktion two hingegen, die kann im Hintergrund laufen. Sobald sie aber GUI-Elemente beeinflussen soll, muss das über die passenden Mitte wie zB QueuedConnections gehen.
bachatero18
User
Beiträge: 41
Registriert: Montag 12. August 2019, 13:57

Erstmal danke für die Antwort.

Also alternative könnte ich sie halt als subprocess öffnen und dann in bestimmten steps abfragen ob der subprocess noch läuft oder wenn die Hauptaufgabe erledigt ist, den subprocess terminieren? Damit läuft es wunderbar war ich aber nicht so begeistert von weil man dann wissen muss wie man den Aufruf machen muss und welche Argumente übergeben werden müssen.
__deets__
User
Beiträge: 14544
Registriert: Mittwoch 14. Oktober 2015, 14:29

Warum drehst du die Threads nicht einfach um, wie vorgeschlagen?
bachatero18
User
Beiträge: 41
Registriert: Montag 12. August 2019, 13:57

Das hab ich tatsächlich überlesen gut Idee, so geht es jetzt auch ohne Fehlermeldung.

Aber wenn ich jetzt abbrechen in der GUI klicke geht er aus der funktion nicht mehr raus. Das sehe ich weil er den print befehl am ende nicht mehr ausführt.

Liegt das an der GUI dort wird sys.exit() benutzt ? Mit welchem befehl sollte ich das ersetzen weil er hat dann ja auch keinen Returnwert und gar nichts

Code: Alles auswählen

import threading
import searching_wheel
import time


def one():
    searching_wheel.main("Beispiel", 35)


def two():
    for i in range(5):
        i += 1
        print("action")
        time.sleep(1)


gui_thread = threading.Thread(target=two)
gui_thread.start()

one()

gui_thread.join()

print('Alle Aufgaben wurden abgeschlossen.')
__deets__
User
Beiträge: 14544
Registriert: Mittwoch 14. Oktober 2015, 14:29

sys.exit beendet das Programm sofort. Wenn du das nicht willst, musst du stattdessen den Hintegrundthread geordnet runterfahren.
__deets__
User
Beiträge: 14544
Registriert: Mittwoch 14. Oktober 2015, 14:29

Um mal zu illustrieren, wie sowas gehen kann:

Code: Alles auswählen

import queue
import threading
import time

def background_task(task_queue):
    while True:
        action = task_queue.get()
        if action == "stop":
            print("stopping")
            break
        else:
            print("working hard on", action)



def main():
    tasks = queue.Queue()
    t = threading.Thread(target=background_task, daemon=True, args=(tasks,))
    t.start()
    tasks.put("important stuff")
    time.sleep(3)
    tasks.put("stop")
    t.join()


# main guard
if __name__ == '__main__':
    main()
Natuerlich muss bei dir die task-queue aus der GUI beschrieben werden, und die get-Methode kennt auch einen timeout oder eine empty-Exception, wenn der Task die ganze Zeit laeuft, und man nur mal schauen will. Aber man muss eben periodisch pruefen, ob man sich beenden soll.
bachatero18
User
Beiträge: 41
Registriert: Montag 12. August 2019, 13:57

Ah okey das muss ich mir genauer angucken das sieht besser als meine Lösung gerade aus.

Aber mal anders rum gefragt wenn die function two() seine arbeitet beendet hat wie kann ich die GUI aus der function two() schließen?
Würde das ebenfalls über queue gehen in dem ich in der GUI regelmäßig abfrage welcher Wert dadrin steht? Wenn ja wann wird die queue wieder geleert sodass ich evtl. beim nächsten aufruf kein Problem mit Fehlzuständen habe?

Oder was gibt es noch für einfache optionen?
Benutzeravatar
__blackjack__
User
Beiträge: 13116
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@bachatero18: Falls wir hier immer noch von Qt reden, könnte man das `finished()`-Signal von `QThread` einfach mit der `close()`-Methode vom Fenster verbinden.
„All religions are the same: religion is basically guilt, with different holidays.” — Cathy Ladman
bachatero18
User
Beiträge: 41
Registriert: Montag 12. August 2019, 13:57

Ich versuche das ganze mal mit den queue hab mich da auch etwas reingelsen denke auch ich hab es so halb verstanden.

Wie bekomme ich mein queue (fertig) in dem fall. Name wird noch geändert in mein searching_wheel übergeben hab es als argument versucht aber das hat irgendwie nicht wirklich geklappt.

Kurz zur erklärung beide Skript sollen regelmäßig abfragen ob in der queue ein True auftaucht. Falls der Hauptarbeitsprozess vorbei ist wird True reingeschrieben und die GUI sieht das. In der GUI wird in der cancel function auch True reingeschrieben und der hauptarbeitsprozess wird unterbrochen mit einem Break.

Code: Alles auswählen

import threading
import searching_wheel
import time
import queue


def gui(fertig,):
    searching_wheel.main("Beispiel", 35)


def work(fertig):
    for i in range(10):
        i += 1
        print("work")
        time.sleep(1)
        try:
            if fertig.get(block=False):
                break
        except queue.Empty:
            continue
    fertig.put(True)


def main():
    fertig = queue.Queue()
    main_work = threading.Thread(target=work, daemon=True, args=(fertig,))
    main_work.start()  # starte die eig. Arbeit

    gui()  # starte die GUI

    main_work.join()
    print('Alle Aufgaben wurden abgeschlossen.')


if __name__ == '__main__':
    main()

Code: Alles auswählen

import sys
from PySide2 import QtWidgets
from ui_searching_wheel import Ui_MainWindow
import queue


class MainWindow(QtWidgets.QMainWindow):

    def __init__(self, label_text, speed):
        super().__init__()
        self.ui = Ui_MainWindow()
        self.ui.setupUi(self)
        self.ui.cancelButton.clicked.connect(self.cancel_button_clicked)
        
    def process_function(self):
        try:
            if fertig.get(block=False):
                self.cancel_button_clicked()
        except queue.Empty:
            pass

    def cancel_button_clicked(self):
        self.timer.stop()
        self.close()  # Schließe das Fenster nach dem Ausführen der Aktionen
        fertig.put(True)



def main(label_text: str = "", speed: int = 5):
    app = QtWidgets.QApplication(sys.argv)
    window = MainWindow(label_text, speed)
    window.show()
    sys.exit(app.exec_())


if __name__ == "__main__":
    main()
bachatero18
User
Beiträge: 41
Registriert: Montag 12. August 2019, 13:57

Habs schon geschafft hatte nur einen Tipp Fehler vielen dank euch. =)
Antworten