Frage

I thought that determining ID for the QThread currently running a function was QThread.currentThreadId(). However I find this does not give the expected results (in PyQt5 with python 3; but I have no reason to believe it would be different with pyqt4 / py 2 hence the generic tags). The thread Id varies in ways that I can't explain, indicating that I can't actually use it, where the QThread instance id varies predictably, indicating that I should be using that to identify currently running thread. To test, I created this:

from PyQt5 import QtCore, QtWidgets
from PyQt5.QtCore import pyqtSignal
import time
import sys

def logthread(caller):
    print('%-25s: %s, %s' % (caller, QtCore.QThread.currentThread(), QtCore.QThread.currentThreadId()))


class Worker(QtCore.QObject):
    done = pyqtSignal()

    def __init__(self, parent=None):
        logthread('worker.__init__')
        super().__init__(parent)

    def run(self, m=10):
        logthread('worker.run')
        for x in range(m):
            y = x + 2
            time.sleep(0.001) 
        logthread('worker.run finished')

        self.done.emit()


class MainWindow(QtWidgets.QWidget):
    def __init__(self, parent=None):
        logthread('mainwin.__init__')
        super().__init__(parent)

        self.worker = Worker()
        self.workerThread = None

        self.btn = QtWidgets.QPushButton('Start worker in thread')
        self.btn2 = QtWidgets.QPushButton('Run worker here')
        layout = QtWidgets.QVBoxLayout(self)
        layout.addWidget(self.btn)
        layout.addWidget(self.btn2)

        self.run()

    def run(self):
        logthread('mainwin.run')

        self.workerThread = QtCore.QThread()
        self.worker.moveToThread(self.workerThread)
        self.worker.done.connect(self.workerDone)
        self.btn.clicked.connect(self.worker.run)
        self.btn2.clicked.connect(self.runWorkerHere)

        self.workerThread.start()
        self.show()

    def workerDone(self):
        logthread('mainwin.workerDone')

    def runWorkerHere(self):
        logthread('mainwin.runWorkerHere')
        self.worker.run()


if __name__ == '__main__':
    app = QtWidgets.QApplication([])
    logthread('main')

    window = MainWindow()
    sys.exit(app.exec_())

When you run it, the first 4 lines printed occur before the event loop is entered and show that the QThread.currentThread() Python id is different in several locations, but the QThread.currentThreadId() is same:

main                     : <PyQt5.QtCore.QThread object at 0x01ABDD00>, <sip.voidptr object at 0x01A4ABC0>
mainwin.__init__         : <PyQt5.QtCore.QThread object at 0x01ABDD50>, <sip.voidptr object at 0x01A4ABC0>
worker.__init__          : <PyQt5.QtCore.QThread object at 0x01ABDDA0>, <sip.voidptr object at 0x01A4ABC0>
mainwin.run              : <PyQt5.QtCore.QThread object at 0x01ABDE90>, <sip.voidptr object at 0x01A4ABC0>

I expected all QThread Python id to be same, but ok maybe several instance of QThread wrap the same C++ thread pointer.

Now click the "Run worker here" button: this just calls the worker.run method directly from GUI thread, so the method should indicate it is running in that thread. This prints these four lines:

mainwin.runWorkerHere    : <PyQt5.QtCore.QThread object at 0x01ABDEE0>, <sip.voidptr object at 0x01A4ABC0>
worker.run               : <PyQt5.QtCore.QThread object at 0x01ABDEE0>, <sip.voidptr object at 0x01A4ABC0>
worker.run finished      : <PyQt5.QtCore.QThread object at 0x01ABDEE0>, <sip.voidptr object at 0x01A4ACC8>
mainwin.workerDone       : <PyQt5.QtCore.QThread object at 0x01ABDEE0>, <sip.voidptr object at 0x01A4ABC0>

Indeed this time the QThread instance ID is same on all lines, nice to see. But the thread id is different in 3rd line, the line that gets printed by the slot as a result of signal in worker.run, yet signal was generated in same thread! Also, it means that the same QThread object can have several underlying thread id.

Now click the "Start Worker". This calls worker.run but in the worker's thread. The 3 lines printed are:

worker.run               : <PyQt5.QtCore.QThread object at 0x01ABDE90>, <sip.voidptr object at 0x01A4ABC0>
worker.run finished      : <PyQt5.QtCore.QThread object at 0x01ABDE90>, <sip.voidptr object at 0x01A4ACC8>
mainwin.workerDone       : <PyQt5.QtCore.QThread object at 0x01ABDEE0>, <sip.voidptr object at 0x01A4ACC8>

The QThread instance ID is different inside the worker.run (first two lines) than in slot, this makes sense. Again the thread id (sip.voidptr) vary in ways that don't make sense to me: I would have expected line 2 to show thread id 0x01A4ABC0, same as first line, rather than same as 3rd line (the slot).

Interestingly, if you replace the thread id output in logthread format by QtWidgets.QApplication.instance().thread(), you find that QThread instance id is always the same as the application QThread instance id except when the worker.run` is run in the separate thread. Even before the application event loop is entered (in other words the application thread id becomes constant only after the event loop starts).

If I'm testing this right, the above indicate that QThread instance ID has consistent and predictable value once the QApplication event loop has started, but that the thread id (currentThreadId()) does not. Hence that whenever I want to test thread location of a function running I should use QThread.currentThread() and possibly compare to app.thread(), but I should avoid currentThreadId(). Anyone see any issues with the way I tested this and the conclusion? If no issues, how does this make sense, given the docs for currentThreadId()? If I made a mistake, what did I do wrong?

War es hilfreich?

Lösung

Your problem mostly stems from the fact that you aren't converting the returned sip.voidptr to an integer. If you instead print int(QThread.currentThreadId()) you get meaningful numbers out. In short what you were looking at was the address of the memory location where the threadId was being stored in, which obviously depends on the apps current memory usage. The contents of those memory addresses always agree though.

You might also be interested to know that the Python threading module gives you the same consistent information (See example below).

One final thing, I feel your app is not thread safe because you move your self.worker object to a QThread, and then directly call a method from the main-thread when you click "run worker here". In my example below, I've instantiated a new worker object in that case just to be safe.

Also, please forgive this conversion of your example to PyQt4 and Python 2.7!

from PyQt4 import QtCore, QtGui
from PyQt4.QtCore import pyqtSignal
import time
import sys
import threading

def logthread(caller):
    print('%-25s: %s, %s,' % (caller, QtCore.QThread.currentThread(), int(QtCore.QThread.currentThreadId())))
    print('%-25s: %s, %s,' % (caller, threading.current_thread().name, threading.current_thread().ident))


class Worker(QtCore.QObject):
    done = pyqtSignal()

    def __init__(self, parent=None):
        logthread('worker.__init__')
        super(Worker, self).__init__(parent)

    def run(self, m=10):
        logthread('worker.run')
        for x in range(m):
            y = x + 2
            time.sleep(0.001) 
        logthread('worker.run finished')

        self.done.emit()


class MainWindow(QtGui.QWidget):
    def __init__(self, parent=None):
        logthread('mainwin.__init__')
        super(MainWindow, self).__init__(parent)

        self.worker = Worker()
        self.workerThread = None

        self.btn = QtGui.QPushButton('Start worker in thread')
        self.btn2 = QtGui.QPushButton('Run worker here')
        layout = QtGui.QVBoxLayout(self)
        layout.addWidget(self.btn)
        layout.addWidget(self.btn2)

        self.run()

    def run(self):
        logthread('mainwin.run')

        self.workerThread = QtCore.QThread()
        self.worker.moveToThread(self.workerThread)
        self.worker.done.connect(self.workerDone)
        self.btn.clicked.connect(self.worker.run)
        self.btn2.clicked.connect(self.runWorkerHere)

        self.workerThread.start()
        self.show()

    def workerDone(self):
        logthread('mainwin.workerDone')

    def runWorkerHere(self):
        logthread('mainwin.runWorkerHere')
        worker = Worker()
        worker.done.connect(self.workerDone)
        worker.run()
        # self.worker.run()


if __name__ == '__main__':
    app = QtGui.QApplication([])
    logthread('main')

    window = MainWindow()
    sys.exit(app.exec_())
Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top