1

I'm trying to correctly display the ID of a thread using QThread but I get some confusing results.

I've already read this: PyQt: Connecting a signal to a slot to start a background operation , but it covers a specific question about slot and signal. This is not the case. I'm not interested in the slot connection order, the focus is infact on understainding what part of the code is running in a specific thread.

Please consider the following code:

from PyQt5.QtCore import QObject, QThread, pyqtSignal


class Worker(QObject):
    started = pyqtSignal()
    finished = pyqtSignal()
    def __init__(self, message):
        super(Worker, self).__init__()
        self.message = message
    def process(self, message):
        i = 0
        cicle = 10000
        j = 0
        while j < 5:
            i = i + 1
            if i % cicle == 0:
                j = float(i/cicle)
                print(message, j)
        print("Worker", int(QThread.currentThread().currentThreadId()))
        self.finished.emit()

worker1 = Worker("Worker 1:")
thread1 = QThread()
thread1.started.connect(lambda: worker1.process(worker1.message))
worker1.finished.connect(thread1.quit)
worker1.finished.connect(worker1.deleteLater)
thread1.finished.connect(thread1.deleteLater)
worker1.moveToThread(thread1)
print("Main app:", int(QThread.currentThread().currentThreadId()))


thread1.start()

I get the following:

Main App: 13420
Worker 1: 1.0
Worker 1: 2.0
Worker 1: 3.0
Worker 1: 4.0
Worker 1: 5.0
Worker 13420

I'm wondering why the first and the last line show the same integer id: why if i move the worker1 in another thread i get the same result?

EDIT: I edit this qustion again to spacify (again) that the problem is not in the connection order. I've tryed this code:

class Worker(QObject):
    started = pyqtSignal()
    finished = pyqtSignal()
    def __init__(self, message):
        super(Worker, self).__init__()
        self.message = message
    def process(self, message):
        i = 0
        cicle = 10000
        j = 0
        while j < 5:
            i = i + 1
            if i % cicle == 0:
                j = float(i/cicle)
                print(message, j)
        # print(int(QThread.currentThread().currentThreadId()))
        print("Worker", int(QThread.currentThread().currentThreadId()))
        self.finished.emit()

worker1 = Worker("Worker 1:")
thread1 = QThread()
worker1.moveToThread(thread1)
print("Main app:", int(QThread.currentThread().currentThreadId()))
thread1.started.connect(lambda: worker1.process(worker1.message))
worker1.finished.connect(thread1.quit)
worker1.finished.connect(worker1.deleteLater)
thread1.finished.connect(thread1.deleteLater)

thread1.start()

with the same results.

Feel free to edit the question if not good enought for the site, and thank you for any suggestion.

Gigino
  • 341
  • 3
  • 16
  • 1
    The problem is that you are connecting the signals ***before*** moving the worker to the thread. See the accepted answer to the duplicate given above for an explanation of why this can sometimes cause problems. To fix it, either connect the signals *after* moving worker, or make sure any pure Python slots (like `process`) are decorated with `pyqtSlot`. – ekhumoro Oct 03 '19 at 12:17
  • Can you please remove the duplicate flag? I've edited my question multiple time to explain why it is a different and more specific question. I think now it's clear the difference. – Gigino Oct 03 '19 at 12:27
  • I see that you've edited your question, but you missed the most vital point, which is that if you connect the signal *before* moving the worker, it will *force the slot to be executed in the main thread*. You need to read the whole of the accepted answer (especially the "edit" section) very carefully to understand why this happens. If you do, you will see that your example has exactly the same problem (and solutions). – ekhumoro Oct 03 '19 at 12:34
  • I have modified again my question, with a second example of code. I'm also trying with the decorator @pyqtSlot, but the results are still the same. As I've already stated, your accepted answer is not helping me too much. Can you please provide a working example of the code above with the problem fixed? This wuold be faster then usless comments. Thank you bery much! – Gigino Oct 03 '19 at 12:43
  • I think you are still missing the point, which is that pyqt creates an internal proxy slot for use with python callables. If you make connections in the way you are doing, the proxy slot will stay in the main thread. So get rid of the redundant `lambda`, and connect the signal directly to the `process` slot (you will also need to get rid of the redundant `message` argument and use `self.message` instead). – ekhumoro Oct 03 '19 at 12:50

2 Answers2

2

Below is a fully working example. Note that this uses the pyqtSlot decorator to fix the problem. If that line is commented out, the example will no longer work, because the worker is moved to the thread after the slots are connected.

from PyQt5.QtCore import *
from PyQt5.QtWidgets import *

class Worker(QObject):
    started = pyqtSignal()
    finished = pyqtSignal()
    def __init__(self, message):
        super(Worker, self).__init__()
        self.message = message
    @pyqtSlot()
    def process(self):
        i = 0
        cicle = 10000
        j = 0
        while j < 5:
            i = i + 1
            if i % cicle == 0:
                j = float(i/cicle)
                print(self.message, j)
        print("Worker", int(QThread.currentThread().currentThreadId()))
        self.finished.emit()

app = QApplication(['test'])

worker1 = Worker("Worker 1:")
thread1 = QThread()
print("Main app:", int(QThread.currentThread().currentThreadId()))
thread1.started.connect(worker1.process)
worker1.finished.connect(thread1.quit)
worker1.finished.connect(worker1.deleteLater)
thread1.finished.connect(app.quit)
worker1.moveToThread(thread1)

thread1.start()

app.exec_()
ekhumoro
  • 115,249
  • 20
  • 229
  • 336
-1

Ok,

I found that the problem was in the lambda function. Simply removing it solved the problem. The connection order does not affect the output. Here is the working example:

from PyQt5.QtCore import QObject, QThread, pyqtSignal


class Worker(QObject):
    started = pyqtSignal()
    finished = pyqtSignal()
    def __init__(self, message):
        super(Worker, self).__init__()
        self.message = message
    def process(self, message):
        i = 0
        cicle = 10000
        j = 0
        while j < 5:
            i = i + 1
            if i % cicle == 0:
                j = float(i/cicle)
                print(message, j)
        print("Worker", int(QThread.currentThread().currentThreadId()))
        self.finished.emit()

worker1 = Worker("Worker 1:")
thread1 = QThread()
thread1.started.connect(worker1.process(worker1.message))
worker1.finished.connect(thread1.quit)
worker1.finished.connect(worker1.deleteLater)
thread1.finished.connect(thread1.deleteLater)
worker1.moveToThread(thread1)
print("Main app:", int(QThread.currentThread().currentThreadId()))


thread1.start()

output:

Main App: 11624
Worker 1: 1.0
Worker 1: 2.0
Worker 1: 3.0
Worker 1: 4.0
Worker 1: 5.0
Worker 12552

There's absolutely no way for a new user to deduce this solution from the acepted answer here: PyQt: Connecting a signal to a slot to start a background operation .

Gigino
  • 341
  • 3
  • 16
  • The order most certainly ***does*** affect the output (if you don't use a `pyqtSlot` decorator). Also, the code currently in your answer is broken in several ways, so it cannot be used to test anything. I take your point that the `lambda` added some confusion, but I think once you understand the underlying issue, it's not too difficult to see where the problem lies. Having said that, I think this is a PyQt misfeature that needs to be fixed at source. Users shouldn't need to know about obscure implementation details in order to use core functionality like signals/slots. – ekhumoro Oct 03 '19 at 13:32