Search code examples
pythonqtpyqtpysideqthread

Can I call a QObject method from main thread after moveToThread() method is called?


I have a worker class

class Worker(QObject):
    finished = Signal()

    def __init__(self, n):
        super().__init__()
        self.a_flag = False
        # self.mutex = QMutex()

    # @Slot()
    def stop(self):
        self.a_flag = True

    @Slot()
    def do_work(self):
        while True:
            if self.a_flag:
                break
            # do some wo
        self.finished.emit()

And somewhere in the controller (from main thread) I am moving it to a new thread

self.worker = Worker(num)
self.worker_thread = QThread()
self.worker.moveToThread(self.worker_thread)

After self.worker.moveToThread(self.worker_thread) the thread affinity of worker is changed and I guess I can't access the members of worker in controller (I am not sure about it but I can argue why). Is there any reference that say's anything about it?

Is it safe to do self.a_flag = True somewhere in controller (main thread) or call stop()?

Is there any workaround to notify do_work to get out of the loop?


Solution

  • Can I call a QObject method from main thread after moveToThread() method is called?

    Yes.
    What thread affinity means queued events or posted events received by QObject will now be handled by new thread (the one where it moved to). moveToThread() will only change the thread affinity of worker (it doesn't really move the object!) and therefore main thread can access worker.

    Now this raise another question: is it safe to modify a_flag in main thread while it is being used by secondary thread?

    Yes.

    Reason we can set worker.a_flag in main thread

    • a_flag is a boolean and setting it is an atomic operation in python. This makes it thread safe and can be set in main thread
    • a_flag is always set by one thread (main thread) and read by another.

    Also note that in case of doubt, use mutex!

    def __init__(self, n):
        super().__init__()
        self.a_flag = False
        self.mutex = QMutex()
    
    def stop(self):
        self.mutex.lock()
        self.a_flag = True
        self.mutex.unlock()
    

    Acquiring lock here not put much overhead, IMO.

    Note that if another thread also set boolean variable after reading it then this operation is not thread safe. Mutex lock is needed in this case. (more on this here]