Правильное использование QThread.currentThreadId()


Я думал, что определение идентификатора для QThread, запущенной в данный момент функции, было QThread.currentThreadId(). Однако я нахожу, что это не дает ожидаемых результатов (в PyQt5 с python 3; но у меня нет оснований полагать, что это будет отличаться с pyqt4 / py 2, следовательно, общие теги). Идентификатор потока изменяется способами, которые я не могу объяснить, указывая, что я не могу фактически использовать его, где идентификатор экземпляра QThread предсказуемо меняется, указывая, что я должен использовать его для идентификации текущего запущенного потока. Тестировать, Я создал это:

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_())

Когда вы запускаете его, первые 4 строки печатаются до ввода цикла событий и показывают, что идентификатор Python QThread.currentThread() отличается в нескольких местах, но QThread.currentThreadId() одинаковый:

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>

Я ожидал, что все QThread Python id будут одинаковыми, но ок, возможно, несколько экземпляров QThread обернут один и тот же указатель потока C++.

Теперь нажмите кнопку" Run worker here": это просто вызовет метод worker.run непосредственно из потока GUI, поэтому метод должен указать его работает в этом потоке. Это печатает эти четыре строки:

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>

Действительно, на этот раз идентификатор экземпляра QThread одинаков во всех строках, приятно видеть. Но идентификатор потока отличается в 3-й строке, строке, которая печатается слотом в результате сигнала в worker.run, но сигнал был сгенерирован в том же потоке! Кроме того, это означает, что один и тот же объект QThread может иметь несколько базовых идентификаторов потока.

Теперь нажмите кнопку "Start Worker". Это вызывает worker.run, но в рабочем потоке. Напечатаны 3 строки являются:

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>

Идентификатор экземпляра QThread отличается внутри worker.run (первые две строки), чем в слоте, это имеет смысл. Опять же, идентификатор потока (sip.voidptr) изменяется способами, которые не имеют смысла для меня: я ожидал бы, что строка 2 покажет идентификатор потока 0x01A4ABC0, такой же, как первая строка, а не такой же, как 3-я строка (слот).

Интересно, что если вы замените вывод идентификатора потока в формате logthread на QtWidgets.QApplication.instance().thread(), вы обнаружите, что QThread идентификатор экземпляра всегда совпадает с приложением QThread идентификатор экземпляра, за исключением случаев, когда работник.run ' выполняется в отдельном потоке. Даже до ввода цикла событий приложения (другими словами, идентификатор потока приложения становится постоянным только после запуска цикла событий).

Если я проверяю это правильно, то выше указано, что идентификатор экземпляра QThread имеет согласованное и предсказуемое значение после запуска цикла событий QApplication, но что идентификатор потока ({21]}) этого не делает. Следовательно, всякий раз, когда я хочу проверить местоположение потока запущенной функции Я должен использовать QThread.currentThread() и, возможно, сравнивать с app.thread(), но я должен избегать currentThreadId(). Кто-нибудь видит какие-либо проблемы с тем, как я проверил это и вывод? Если нет проблем, то как это имеет смысл, учитывая документы для currentThreadId()? Если я ошибся, что я сделал не так?

1 5

1 ответ:

Ваша проблема в основном связана с тем, что вы не конвертируете возвращенный sip.voidptr к целому числу. Если вы вместо этого напечатаете int(QThread.currentThreadId()), вы получите значимые числа. Короче говоря, вы смотрели на адрес ячейки памяти, в которой хранился threadId, что, очевидно, зависит от текущего использования памяти приложениями. Однако содержание этих адресов памяти всегда совпадает.

Вам также может быть интересно узнать, что модуль Python threading предоставляет вам та же согласованная информация (см. пример ниже).

И последнее, я чувствую, что ваше приложение не является потокобезопасным, потому что вы перемещаете свой объект self.worker в QThread, а затем непосредственно вызываете метод из главного потока, когда нажимаете "запустить рабочий здесь". В приведенном ниже примере я на всякий случай создал экземпляр нового объекта worker.

Также, пожалуйста, простите это преобразование вашего примера в PyQt4 и 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_())