Skip to content
Advertisement

How to assign variables to worker Thread in Python PyQt5?

I have designed a GUI program using pyqt5. I have a main thread and a worker thread. When the GUI starts, i get some inputs from user such as age, name, … and i want to process the inputs in worker. For example How can i send the inputs which i get using self.ui.firstname.text() to the worker? in other words, self.ui.firstname.text() is not accessible inside the worker and i am not able to get the value of the first name input. To better say, I want to access the GUI inputs from the worker thread but i dont know how can i do this.

class Worker(QObject):
    finished = pyqtSignal()
    
    def run(self):
        firstname = self.ui.firstname.text()

        for i in range(10):
        
            sleep(1)
            print(firstname )
        self.finished.emit()

class MyAPP(QDialog):
    def __init__(self,parent=None):
        super().__init__(parent)

        self.setFixedSize(1180, 850)
        self.ui = Ui_Dialog()
        self.ui.setupUi(self)
        self.runWorkerBtn.clicked.connect(self.runLongTask)

   def runLongTask(self):
       
        self.thread = QThread()
     
        self.worker = Worker()
  
        self.worker.moveToThread(self.thread)
       
        self.thread.started.connect(self.worker.run)
        self.worker.finished.connect(self.thread.quit)
        self.worker.finished.connect(self.worker.deleteLater)
        self.thread.finished.connect(self.thread.deleteLater)
        self.thread.start()



I really appreciate any help on this problem

Advertisement

Answer

You can acomplish this by adding a new property to your worker class. Just make sure it doesn’t shadow any other property, method, slot or signal from the object. With this, you can now pass data to your thread from initialization, via the parameter area:

class Worker(QObject):
    finished = pyqtSignal()
    def __init__(self, text="Foo"):
        QObject.__init__(self)
        self.firstNameText = text

On your Main Thread, when you create the worker object, you can put pass data using that parameter we just set:

# On Main Thread
wk = Worker("Hello World")
wk.moveToThread(...)

Now, when we print the firstNameText variable on the Other Thread, it will print: Hello World.


But if you need to pass data to a thread while it’s already running, that’s another story: we must think of thread-safety and synchronization. Let’s use the firstNameText variable as an example. Imagine it starts set with the string Foo:

  1. What if during the Main Thread, we resolve to change that value to Oof and at the same time, print it from the Other Thread? Which value will the Other Thread print? We cannot know for sure, it’s either Foo or Oof.
  2. What if during the Main Thread, we resolve to change that value to Oof and at the same time, the Other Thread tries to change that value to ABC and then print it. Which value will be printed by the Other Thread? Again, we cannot know for sure, it’s either Oof or ABC.

If you are still confused about this example, just go read about how multithreading or multiprocessing works.

In order to prevent this problem, we must use a mutex to synchronize access to the firstNameText variable between both threads. Fortunately, Qt already provides the QMutex class, so let’s use it.

When we create the Thread on the Main Thread, we must share the QMutex:

# ...
mt = QMutex()
th = QThread()
wk = Worker()
wk._mutex = mt
wk.moveToThread(th)
# ...

If you would like to modify this variable in any Other Thread or in the Main Thread to another value, for example: wk._mutex = None, this is a problem if both threads are still running at the same time. You’re not having exclusive access to this variable, so be careful. Just do it without any mutexes, when you know that you have only 1 thread accessing this property.

But if both threads get to read only the Worker._mutex property, wether it is to lock or unlock it, we don’t need to worry about thread-safety anymore. The state of the property will never change, so it doesn’t matter if the Main Thread or the Other Thread is accessing it, the worker’s instance will be still there, being stored by this property.

And now, when we want to pass a value from the Main Thread Side to the Other Thread, we can simply write:

# On main thread:
wk._mutex.lock()
wk.firstNameText = myInput.text()
wk._mutex_unlock()

# On the other thread:
self._mutex.lock()
text = self.firstNameText
self._mutex.unlock()

Everything inside the lock and unlock calls are thread-safe between the threads locking these locks. If you never unlock the locked lock, the other threads which try to lock it once more will get stuck on this function call, so be very careful when using them. Never forget to unlock them.

To keep it simple and easy to use, we can put all of that inside two separate methods, a setter and a getter:

class Worker(QObject):
    finished = pyqtSignal()

    def __init__(self):
        QObject.__init__(self)
        self.firstNameText = ""
        self._mutex = QMutex()

    def getFirstName(self):
        self._mutex.lock()
        text = self.firstNameText
        self._mutex.unlock()
        return text

    def setFirstName(self, text):
        self._mutex.lock()
        self.firstNameText = text
        self._mutex.unlock()
    
    def run(self):
        firstname = self.getFirstName()
        for i in range(10):
            sleep(1)
            print(firstname)
        self.finished.emit()

And from your Main Thread, at any moment, you can call:

wk.setFirstName("Hello World 2")

If the Other Thread is still running, that’s the value that’s going to be printed on the terminal.

User contributions licensed under: CC BY-SA
6 People found this is helpful
Advertisement