Pj Toopmuch
Pj Toopmuch

Reputation: 147

Use PySide6 in thread

Qt has a promising SCXML module. Since PySCXML is obsolete, there is no other native python scxml library, which lets me run a scxml statemachine. That's why I try PySide6.

Since I don't need any Qt despite of the scxml library, I thought about running the QCoreApplication in a seperate thread, in order to have the event-loop right there. According to the documentation QScxmlStateMachine needs one.

Unfortunately my start_statemachine() method doesn't return, but the statemachine starts working.

Any advice on how to start a QScxmlStateMachine in a thread is welcomed.

from PySide6.QtCore import QCoreApplication, QObject
from PySide6.QtScxml import QScxmlStateMachine
from PySide6.QtCore import QTimer
import threading


def start_statemachine(filename):
    app = QCoreApplication()
    mysm = MyStateMachine(filename)
    mysm.start_sm()
    app.exec()


class MyStateMachine(QObject):

    def __init__(self, filename):
        super(MyStateMachine, self).__init__()
        self.sm = QScxmlStateMachine.fromFile(filename)
        self.counter = 0
        self.timer = QTimer()
        self.timer.setInterval(2000)
        self.timer.timeout.connect(self.recurring_timer)
        self.timer.start()

    def start_sm(self):
        print('starting statemachine')
        self.sm.setRunning(True)

    def recurring_timer(self):
        print(self.sm.activeStateNames())
        self.counter += 1
        print("Counter: %d" % self.counter)
        print('statemachine running status: ' + str(self.sm.isRunning()))


if __name__ == '__main__':
    x = threading.Thread(target=start_statemachine('statemachine.scxml'))
    x.start() #won't be reached
    while True:
        pass #do something else
    x.join()

Upvotes: 0

Views: 769

Answers (1)

musicamante
musicamante

Reputation: 48241

The thread target needs to be a reference to a function that will be called in the external thread, but you're not running start_statemachine() in another thread: you're actually executing it in place:

x = threading.Thread(target=start_statemachine('statemachine.scxml'))
                                              ^^^^^^^^^^^^^^^^^^^^^^

Your program is stuck there, no thread is even created because the constructor is still "waiting" for start_statemachine() to return, and since exec() is blocking, nothing else happens.

A basic solution could be to use a lambda:

x = threading.Thread(target=lambda: start_statemachine('statemachine.scxml'))

But you'll need access to the application in order to be able to quit it: x.join() won't do nothing, because the QCoreApplication event loop will keep going, so a possibility is to create a basic class that provides a reference to the application:

class StateMachineWrapper:
    app = None
    def __init__(self, filename):
        self.filename = filename

    def start(self):
        self.app = QCoreApplication([])
        mysm = MyStateMachine(self.filename)
        mysm.start_sm()
        self.app.exec()

# ...

if __name__ == '__main__':
    statemachine = StateMachineWrapper('statemachine.scxml')
    x = threading.Thread(target=statemachine.start)
    x.start()
    while True:
        pass #do something else

    if statemachine.app:
        statemachine.app.quit()

    x.join()

Upvotes: 1

Related Questions