QThread多线程
Qt中多线程最常用的方法是QThread,QThread是Qt中所有线程控制的基础,每个QThread实例代表并控制一个线程。
QThread 有两种使用方式,子类化或实例化。
- 子类化QThread 需要重写run0函数并在该函数中进行多线程运算,这种方式相对简单一些;
- 实例化QThread 需要通过QObject.moveTothread(targetThread:QThread)函数接管多线程类
from PySide6.QtCore import QThread
QThread(parent: Union[PySide6.QtCore.QObject, NoneType] = None) -> None
官方介绍
QThread 对象管理程序中的一个控制线程。QThreads 在 run()中开始执行。默认情况下,run()通过调用 exec()来启动事件循环,并在线程内运行 Qt 事件循环。
您可以通过使用 moveTothread()将工作器对象移动到线程来使用工作线程。
class Worker(QObject):
Q_OBJECT
# public slots
def doWork(parameter):
result = QString()
/* ... here is the expensive or blocking operation ... */
resultReady.emit(result)
# signals
def resultReady(result):
class Controller(QObject):
Q_OBJECT
workerThread = QThread()
# public
Controller(){
worker = Worker()
worker.moveTothread(workerThread)
workerThread.finished.connect(worker.deleteLater)
self.operate.connect(worker.doWork)
worker.resultReady.connect(self.handleResults)
workerThread.start()
~Controller(){
workerThread.quit()
workerThread.wait()
# public slots
def handleResults():
# signals
def operate():
然后,Worker 插槽中的代码将在单独的线程中执行。
可以自由地将工人的插槽连接到任何线程中来自任何对象的任何信号。跨不同线程连接信号和插槽是安全的,这要归功于一种称为 queued 的机制。
使代码在单独的线程中运行的另一种方法是子类QThread并重新实现run()。例如:
class WorkerThread(QThread):
Q_OBJECT
def run():
result = QString()
/* ... here is the expensive or blocking operation ... */
resultReady.emit(result)
# signals
def resultReady(s):
def startWorkInAThread(self):
workerThread = WorkerThread(self)
workerThread.resultReady.connect(self.handleResults)
workerThread.finished.connect(workerThread.deleteLater)
workerThread.start()
在该示例中,线程将在 run 函数返回后退出。线程中不会运行任何事件循环,除非调用 exec()。
重要的是要记住,QThread 实例是实例化它的旧线程,而不是在调用 run()的新线程中。这意味着 QThread 的所有排队插槽和调用的方法都将在旧线程中执行。因此,希望在新线程中调用槽的开发人员必须使用工作线程-对象方法;新插槽不应直接实现到子类化的 QThread 中。lives in
与排队的插槽或调用的方法不同,直接在 QThread 对象上调用的方法将在调用该方法的线程中执行。当子类化 QThread 时,请记住构造函数在旧线程中执行,而 run()在新线程中执行。如果从两个函数访问成员变量,则从两个不同的线程访问该变量。检查这样做是否安全。
管理线程
- QThread 会在线程start()和 done()时发送信号,或者可以使用 isDone()和 isRunning()来查询线程的状态。
- 可以通过调用 exit()或 quit()来停止线程。在极端情况下,您可能希望强制终止()正在执行的线程。但是,这样做是危险且不鼓励的。有关详细信息,请阅读 terminate()和 setTerminationEnabled()的文档。
从Qt 4.8开始,可以通过将finish()信号连接到deleteLater()来释放刚刚结束的线程中的对象。
使用 wait()阻止调用线程,直到另一个线程完成执行(或直到指定的时间过去)。
QThread 还提供静态的、独立于平台的睡眠函数:sleep()、msleep()和 usleep()分别允许全秒、毫秒和微秒分辨率。这些功能在Qt 5.0中公开。
- 静态函数和 currentThread()返回当前正在执行的线程的标识符。前者返回线程的平台特定 ID;后者返回一个 QThread 指针。
- currentThreadId()要选择将为您的线程指定的名称(例如,由 Linux 上的命令标识),您可以在启动线程之前调用 setObjectName()。如果不调用 setObjectName(),则为线程指定的名称将是线程对象的运行时类型的类名(例如,在 MandelbrQt 示例的情况下,因为这是 QThread 子类的名称)。请注意,这目前不适用于 Windows 上的发布版本。
class WorkThread(QThread):
count = int(0)
countSignal = Signal(int)
def __init__(self):
super(WorkThread,self).init()
def run(self):
self.flag = True
while self.flag:
self.count += 1
self.countSignal.emit(self.count)
time.sleep(1)
# 上述代码的启动方式如下:
if __name__ == '__main__':
self.thread = WorkThread()
self.threadcountSignal.connect(self.flush)
self.label = QLabel('0')
self.thread.start()
def flush(self,count):
self.label.setText(str(count))
用法介绍
实例化代码也需要新建一个类实例化之后需要通过moveTothread()函数让QThread接管,标准模板如下.
# -*- coding: UTF-8 -*-
# File date: Hi_2023/3/13 21:52
# File_name: 01-实例化多线程模板.py
import time
from PySide6.QtCore import QObject,Signal,QThread
class work(QObject):
count = int(0)
conuntSignal = Signal(int)
def __init__(self):
super().__init__()
def work(self):
self.flag = True
while self.flag:
self.count += 1
self.conuntSignal.emit(self.count)
time.sleep(1)
if __name__ == '__main__':
def flush():
...
worker = work()
thread = QThread()
worker.moveTothread(thread)
worker.conuntSignal.connect(flush)
thread.started.connect(worker.work)
thread.start()
上面是QThread的最基础的用法。
QThread会在线程启动和结束时发射 started 信号和 finished 信号,也可以使用函数isFinished0和isRunning0查询线程的状态。
从Qt4.8开始,可以通过将inished信号连接到 QObject.deleteLater()函数来释放刚刚结束的线程中的对象。
如果要终止线程,则可以使用函数exit()或quit()。
- 在极端情况下,要使用terminate0函数强制终止正在运行的线程非常危险(并不鼓励这样做)
- 同时要确保在terminate0函数之后使用wait()函数
- 使用wait()函数可以阻塞调用线程,直到另一个线程完成执行(或直到经过指定的时间)。
- 从 Qt 5.0 开始,QThread 还提供了静态的、与平台无关的睡眠函数,如 sleep()、msleep()和 usleep(),分别允许整秒、毫秒和微秒计时。
- 需要注意的是,一般不使用函数wait()和sleep(),因为Qt是一个事件驱动的框架。
- 可以使用finished 信号代替 wait()函数
- 使用QTimer代替 sleep0函数。
- 使用静态函数currentThreadId()和 currentThread()可以返回当前执行线程的标识符
- 前者返回线程的平台特定ID,后者返回一个 QThread 指针。
QThread类中常用方法
方法 | 参数/返回值类型 | 说明 |
---|---|---|
eventDispatcher() | PySide6.QtCore.QAbstractEventDispatcher | 返回线程的事件分派器对象的指针。如果线程不存在事件分派器,则此函数返回None。 |
exec() | int | 进入事件循环并等待,直到调用exit(),返回传递给exit(的值。如果通过quit()调用exit(),则返回的值为0。 此函数旨在从run()内调用。需要调用此函数来启动事件处理。 |
exec_() | int | 已经启用调用exec() |
isFinished() | bool | 如果线程完成,则返回true;否则返回false。 |
isInterruptionRequested() | bool | 如果应停止此线程上运行的任务,则返回true。 可以通过requestInterrupt()请求中断。 此函数可用于使长时间运行的任务完全可中断。从不检查或执行此函数返回的值是安全的,但建议在长时间运行的函数中定期执行此操作。注意不要经常打电话,以保持开销低。 |
isRunning() | bool | 如果线程正在运行,则返回true;否则返回false。 |
loopLevel() | int | 返回线程的当前事件循环级别。 |
priority() | priority | 返回正在运行的线程的优先级(见下表)。如果线程未运行,则此函数返回InheritPriority。 |
requestInterruption() | 请求线程中断。该请求是建议性的,由线程上运行的代码决定是否以及如何响应该请求。此函数不会停止线程上运行的任何事件循环,也不会以任何方式终止它。 | |
setEventDispatcher(eventDispatcher) | eventDispatcher – PySide6.QtCore.QAbstractEventDispatcher | 将线程的事件分派器设置为eventDispatcher。只有在尚未为线程安装事件分派器的情况下,这才是可能的。 当QCoreApplication被实例化时,会自动为主线程创建事件分派器,并在辅助线程的start()上创建事件分派程序。 此方法获取对象的所有权。 |
setPriority(priority) | priority – Priority | 此函数设置正在运行的线程的优先级。如果线程未运行,则此函数不执行任何操作并立即返回。使用start()启动具有特定优先级的线程。 优先级参数可以是QThread::priority枚举中的任何值,InheritPriority除外。 优先级参数的效果取决于操作系统的调度策略。特别是,在不支持线程优先级的系统上(例如在Linux上,请参见 |
setStackSize(stackSize) | stackSize – uint | 将线程的最大堆栈大小设置为stackSize。如果stackSize大于零,则最大堆栈大小设置为stackSize字节,否则最大堆栈大小由操作系统自动确定。 |
stackSize() | uint | 返回线程的最大堆栈大小(如果使用setStackSize()设置);否则返回零。 |
wait([deadline=QDeadlineTimer(QDeadlineTimer.Forever)]) | PARAMETERS deadline – PySide6.QtCore.QDeadlineTimer RETURN TYPE bool | 阻塞线程,直到满足以下任一条件: 与此QThread对象关联的线程已完成执行(即,当它从run()返回时)。如果线程完成,此函数将返回true。如果线程尚未启动,它也会返回true。 截止日期已到。如果达到最后期限,此函数将返回false。 设置为QDeadlineTimer::Forever(默认值)的截止时间计时器永远不会超时:在这种情况下,该函数仅在线程从run()返回或线程尚未启动时返回。 这提供了与POSIX pthread_join()函数类似的功能。 |
wait(time) | PARAMETERS deadline – PySide6.QtCore.QDeadlineTimer RETURN TYPE bool | 阻塞线程,直到满足以下任一条件:其他同上 |
run() | 线程的起点。调用start()后,新创建的线程将调用此函数。默认实现只调用exec()。 您可以重新实现此函数以促进高级线程管理。从该方法返回将结束线程的执行。 | |
[slots]exit([retcode=0]) | retcode – int | 使用返回码告诉线程的事件循环退出。 调用此函数后,线程离开事件循环并从对exec()的调用中返回。exec()函数返回返回代码。 按照惯例,返回代码为0表示成功,任何非零值都表示错误。 请注意,与同名的C库函数不同,此函数确实返回给调用者——停止的是事件处理。 在此线程中不会再启动QEventLoops,直到再次调用exec()。如果exec()中的事件循环没有运行,那么对exec()的下一次调用也将立即返回。 |
[slots]quit() | 告诉线程的事件循环退出,返回代码为0(成功)。相当于调用出口(0)。 如果线程没有事件循环,则此函数不起作用。 | |
[slots]start([priority=QThread.Priority.InheritPriority]) | priority – Priority | 通过调用run()开始执行线程。操作系统将根据优先级参数调度线程。如果线程已经在运行,则此函数不执行任何操作。 优先级参数的效果取决于操作系统的调度策略。特别是,在不支持线程优先级的系统上(如在Linux上,请参阅sched_setscheduler文档以了解更多详细信息),优先级将被忽略。 |
[slots]terminate() | 终止线程的执行。根据操作系统的调度策略,线程可能会立即终止,也可能不会立即终止。请务必在terminate()之后使用wait()。 当线程终止时,等待线程完成的所有线程都将被唤醒。 此功能很危险,不鼓励使用。线程可以在其代码路径的任何位置终止。修改数据时可以终止线程。线程没有机会自行清理、解锁任何持有的互斥锁等。简而言之,只有在绝对必要时才使用此函数。 可以通过调用setTerminationEnabled()显式启用或禁用终止。在禁用终止时调用此函数会导致延迟终止,直到重新启用终止。有关详细信息,请参阅setTerminationEnabled()的文档。 | |
[Static]currentThread() | PySide6.QtCore.QThread | 返回指向管理当前执行线程的QThread的指针。 |
[Static]idealThreadCount() | int | 返回此进程可以并行运行的理想线程数。这是通过查询此进程可用的逻辑处理器数(如果此操作系统支持)或系统中的逻辑处理器总数来完成的。如果两个值都无法确定,此函数返回1。 |
[Static]msleep(arg__1) | arg__1 – int | 强制当前线程休眠毫秒。 如果需要等待给定条件发生变化,请避免使用此函数。相反,将插槽连接到指示更改的信号或使用事件处理程序(请参阅event())。 此功能不能保证准确性。在重载条件下,应用程序的休眠时间可能超过毫秒。一些操作系统可能将毫秒舍入为10毫秒或15毫秒。 |
[Static]setTerminationEnabled([enabled=true]) | enabled – bool | 根据启用的参数启用或禁用当前线程的终止。该线程必须已由QThread启动。 如果启用为false,则禁用终止。以后对terminate()的调用将立即返回而不起作用。相反,将延迟终止,直到启用终止。 如果启用为true,则启用终止。以后调用terminate()将正常终止线程。如果终止被延迟(即,在禁用终止的情况下调用terminate()),则此函数将立即终止调用线程。请注意,在这种情况下,此函数不会返回。 |
[Static]sleep(arg__1) | arg__1 – int | 强制当前线程休眠秒。 如果需要等待给定条件发生变化,请避免使用此函数。相反,将插槽连接到指示更改的信号或使用事件处理程序(请参阅event())。 此功能不能保证准确性。在重载条件下,应用程序可能会休眠超过秒。 |
[Static]usleep(arg__1) | arg__1 – int | 强制当前线程休眠usecs微秒。 如果需要等待给定条件发生变化,请避免使用此函数。相反,将插槽连接到指示更改的信号或使用事件处理程序(请参阅event())。 此功能不能保证准确性。在重载条件下,应用程序可能比usecs休眠更长时间。一些操作系统可能将usecs舍入到10ms或15ms;在Windows上,它将舍入为1ms的倍数。 |
[Static]yieldCurrentThread() | 将当前线程的执行交给另一个可运行的线程(如果有的话)。 注意,操作系统决定切换到哪个线程。 |
-
优先级枚举值PySide6.QtCore.QThread.Priority
此枚举类型指示操作系统应如何调度新创建的线程。Constant Description QThread.IdlePriority 仅在没有其他线程运行时计划。 QThread.LowestPriority 计划频率低于低优先级。 QThread.LowPriority 计划频率低于正常优先级。 QThread.NormalPriority 操作系统的默认优先级。 QThread.HighPriority 比正常优先级更频繁地安排。 QThread.HighestPriority 比高优先级更频繁地安排。 QThread.TimeCriticalPriority 尽可能频繁地安排。 QThread.InheritPriority 使用与创建线程相同的优先级。这是默认值。
QThread类信号
信号 | 说明 |
---|---|
finished() | 该信号在相关线程完成执行之前从其发出。 发出此信号时,事件循环已停止运行。除延迟删除事件外,线程中不会再处理其他事件。该信号可以连接到deleteLater(),以释放该线程中的对象。 如果使用terminate()终止了关联的线程,则不确定该信号是从哪个线程发出的。 |
started() | 当相关线程开始执行时,在调用run()函数之前,该信号从该线程发出。 |
QThread的使用方法
本案例涉及两个文件,两个脚本的功能是一样的,只是实现方法稍微不同,前者采用子类化的方式,后者采用实例化的方式,内容稍微不同,
QThread 子类化的使用
# -*- coding: UTF-8 -*-
# File date: Hi_2023/3/13 22:33
# File_name: 02- QThread 子类化的使用.py
from PySide6.QtCore import Signal,QThread,Qt
from PySide6.QtWidgets import QMainWindow,QWidget,QLabel,QApplication,QPushButton,QHBoxLayout
from PySide6.QtGui import QFont
import sys
import time
class WorkThread(QThread):
count = int(0)
countSignal = Signal(int)
def __init__(self):
super(WorkThread,self).__init__()
def run(self):
self.flag = True
while self.flag:
self.count += 1
self.countSignal.emit(self.count)
time.sleep(1)
class MainWindow(QMainWindow):
def __init__(self):
super(MainWindow,self).__init__()
self.setWindowTitle('QThread demo')
self.resize(515,208)
self.widget = QWidget()
self.buttonStart = QPushButton('开始')
self.buttonStop = QPushButton('结束')
self.label = QLabel('0')
self.label.setFont(QFont("Adobe Arabic",28))
self.label.setAlignment(Qt.AlignCenter)
layout = QHBoxLayout()
layout.addWidget(self.label)
layout.addWidget(self.buttonStart)
layout.addWidget(self.buttonStop)
self.widget.setLayout(layout)
self.setCentralWidget(self.widget)
self.buttonStart.clicked.connect(self.onStart)
self.buttonStop.clicked.connect(self.onStop)
self.thread = WorkThread()
self.thread.countSignal.connect(self.flush)
self.thread.started.connect(lambda: self.statusBar().showMessage('多线程started信号'))
self.thread.finished.connect(self.finished)
def flush(self,count):
self.label.setText(str(count))
def onStart(self):
self.statusBar().showMessage('button start.')
print('button start.')
self.buttonStart.setEnabled(False)
self.thread.start()
def onStop(self):
self.statusBar().showMessage('button stop.')
self.thread.flag = False
self.thread.quit()
def finished(self):
self.statusBar().showMessage('多线程finish信号')
self.buttonStart.setEnabled(True)
if __name__ =="__main__":
app = QApplication(sys.argv)
demo = MainWindow()
demo.show()
sys.exit(app.exec())
QThread实例化
# -*- coding: UTF-8 -*-
# File date: Hi_2023/3/13 22:33
# File_name: 03- QThread实例化.py
from PySide6.QtCore import Signal,QObject,QThread,Qt
from PySide6.QtWidgets import QMainWindow,QWidget,QLabel,QApplication,QPushButton,QHBoxLayout
from PySide6.QtGui import QFont
import sys
import time
class Work(QObject):
count = int(0)
countSignal = Signal(int)
def __init__(self):
super(Work,self).__init__()
def work(self):
self.flag = True
while self.flag:
self.count += 1
self.countSignal.emit(self.count)
time.sleep(1)
class MainWindow(QMainWindow):
def __init__(self):
super(MainWindow,self).__init__()
self.setWindowTitle('QThread demo')
self.resize(515,208)
self.widget = QWidget()
self.buttonStart = QPushButton('开始')
self.buttonStop = QPushButton('结束')
self.label = QLabel('0')
self.label.setFont(QFont("Adobe Arabic",28))
self.label.setAlignment(Qt.AlignCenter)
layout = QHBoxLayout()
layout.addWidget(self.label)
layout.addWidget(self.buttonStart)
layout.addWidget(self.buttonStop)
self.widget.setLayout(layout)
self.setCentralWidget(self.widget)
self.buttonStart.clicked.connect(self.onStart)
self.buttonStop.clicked.connect(self.onStop)
self.thread = QThread()
self.worker = Work()
self.worker.countSignal.connect(self.flush)
self.worker.moveTothread(self.thread)
self.thread.started.connect(self.worker.work)
self.thread.finished.connect(self.finished)
def flush(self,count):
self.label.setText(str(count))
def onStart(self):
self.statusBar().showMessage('button start.')
self.buttonStart.setEnabled(False)
self.thread.start()
def onStop(self):
self.statusBar().showMessage('button stop.')
self.worker.flag = False
self.thread.quit()
def finished(self):
self.statusBar().showMessage('多线程finish.')
self.buttonStart.setEnabled(True)
if __name__ =="__main__":
app = QApplication(sys.argv)
demo = MainWindow()
demo.show()
sys.exit(app.exec())