QThread.currentThreadId()的正确使用方法
我原以为要确定正在运行某个函数的 QThread
的 ID,只需要用 QThread.currentThreadId()
就可以了。然而,我发现这个方法的结果并不如我预期(在 PyQt5 和 Python 3 中;但我没有理由相信在 PyQt4 或 Python 2 中会有所不同,所以我用了通用标签)。线程 ID 的变化让我感到困惑,这表明我不能真正使用它,而 QThread
实例的 ID 则是可以预测的,这说明我应该用这个来识别当前正在运行的线程。为了测试这一点,我创建了以下代码:
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_())
当你运行这段代码时,前四行打印的内容是在进入事件循环之前的,这显示了 QThread.currentThread()
的 Python ID 在多个地方是不同的,但 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++ 线程指针,这也说得通。
现在点击“在这里运行工作者”按钮:这只是直接从 GUI 线程调用 worker.run
方法,所以这个方法应该表明它是在那个线程中运行的。这会打印出以下四行:
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
实例 ID 是相同的,看到这一点真不错。但在第三行中,线程 ID 是不同的,这一行是由信号在 worker.run
中的槽打印出来的,然而信号是在同一个线程中生成的!这也意味着同一个 QThread
对象可以有多个底层线程 ID。
现在点击“启动工作者”。这会在工作者的线程中调用 worker.run
。打印出的三行是:
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>
在 worker.run
中(前两行)的 QThread
实例 ID 与槽中的不同,这很有道理。同样,线程 ID(sip.voidptr
)的变化让我感到困惑:我本以为第二行会显示线程 ID 0x01A4ABC0,和第一行相同,而不是和第三行(槽)相同。
有趣的是,如果你把 logthread
格式中的线程 ID 输出替换为 QtWidgets.QApplication.instance().thread()
,你会发现 QThread
实例 ID 在应用程序的 QThread 实例 ID 中是始终相同的,除非 worker.run
在单独的线程中运行。甚至在应用程序事件循环开始之前(换句话说,应用程序线程 ID 只有在事件循环开始后才会变得恒定)。
如果我测试得没错,上述内容表明 QThread
实例 ID 一旦 QApplication
事件循环开始,就有一致且可预测的值,但线程 ID(currentThreadId()
)则没有。因此,每当我想测试某个函数运行的线程位置时,我应该使用 QThread.currentThread()
,并可能与 app.thread()
进行比较,但我应该避免使用 currentThreadId()
。有人对我测试的方式和结论有问题吗?如果没有问题,这和 currentThreadId()
的文档有什么关系?如果我犯了错误,我错在哪里?
1 个回答
你的问题主要是因为你没有把返回的sip.voidptr转换成整数。如果你打印 int(QThread.currentThreadId())
,你会得到有意义的数字。简单来说,你之前看到的是线程ID存储的内存地址,这个地址显然会根据应用程序当前的内存使用情况而变化。不过,这些内存地址的内容总是保持一致的。
你可能还会想知道,Python的线程模块也能给你相同的一致信息(见下面的例子)。
最后,我觉得你的应用程序不安全,因为你把 self.worker
对象移动到了QThread中,然后在主线程中直接调用了“运行工作者”的方法。在我下面的例子中,我为了安全起见,实例化了一个新的工作者对象。
另外,请原谅我把你的例子转换成了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_())