5

I am using pyqtSignal to send a python list as an argument from worker thread to main thread. When does qt create a copy of the object being passed as argument. According to: http://www.embeddeduse.com/2013/06/29/copied-or-not-copied-arguments-signals-slots/ the qt should make a copy of the object. However, in the example below the main thread can change the contents of the list being sent from another thread.

import sys
import time
from PyQt5.QtCore import QThread, QObject, pyqtSlot, pyqtSignal
from PyQt5.QtWidgets import QApplication

class ClassProcessing(QObject):

    py_sig_send_data = pyqtSignal(list)

    def __init__(self):
        super().__init__()
        # initialize some variables
        self.data = [1, 2, 3, 4, 5]

    def worker(self):
        print(self.data)
        self.py_sig_send_data.emit(self.data)
        time.sleep(1)
        print("modfied data in thread", self.data)

class ClassProcessingThread(QObject):
    def __init__(self):
        super().__init__()
        self.objThread = QThread()
        self.objThread_id = 1
        self.objThread_finished = False
        self.processing = ClassProcessing()
        self.processing.moveToThread(self.objThread)
        self.objThread.started.connect(self.processing.worker)
        self.objThread.start()

class SomeClass(QObject):
    def __init__(self):
        super().__init__()

    @pyqtSlot(list)
    def receive_data(self, data):
        print("received data", data)
        data[1] = 42
        print("modified data", data)

def main():
    app = QApplication(sys.argv)
    processing_thread = ClassProcessingThread()
    some_class = SomeClass()
    processing_thread.processing.py_sig_send_data.
        connect(some_class.receive_data)
    sys.exit(app.exec_())

if __name__ == '__main__':
    main()

The output:

[1, 2, 3, 4, 5]
received data [1, 2, 3, 4, 5]
modified data [1, 42, 3, 4, 5]
modified data in thread [1, 42, 3, 4, 5]

Can someone please explain to me how to pass a list in a pyqtSignal in a thread-safe manner. Thanks.

ekhumoro
  • 115,249
  • 20
  • 229
  • 336
John
  • 53
  • 1
  • 4

1 Answers1

4

PyQt doesn't behave in the same way as Qt when it comes to passing container types between threads using signals.

Specifically, there is no automatic conversion [1] to the equivalent Qt types, and therefore no implicit copying. PyQt does provide a mechanism to explicitly request such conversions, though. To do that, you can define a custom signal using either QVariantList or QVariantMap:

    py_sig_send_data = pyqtSignal('QVariantList')

However, it's important to note that QVariantMap only supports string keys.

All in all, though, it's probably simpler, clearer and safer to just explictly copy mutable python types before passing them via signals across threads.

[1] Or at least, not any more.

ekhumoro
  • 115,249
  • 20
  • 229
  • 336
  • Interesting this isn't stated anywhere more explicitly. I have searched extensively but apparently not in the right place. The explicit copy is probably useful for sending the signal once; the data copied to data_buffer and data_buffer passed as argument. What about continuous sending (e.g. 50 times per second)? The sending and the receiving thread could be accessing the data_buffer at the same time. – John Mar 31 '15 at 07:28
  • @John. If you want to avoid copying, you'll need to use some kind of [locking mechanism](https://en.wikipedia.org/wiki/Lock_%28computer_science%29). – ekhumoro Mar 31 '15 at 16:41