两种多线程设计模式
继承QThread方式:
将线程对象和工作对象合二为一,工作逻辑在run()方法中实现。
- 继承自QThread,并重写run()方法,在run()方法中执行耗时操作。
- 线程启动时,run()方法被调用,线程结束,run()方法返回。
- 这种方式将线程和任务绑定在一起,每个线程对象只能执行一个任务(run方法中定义的任务)。
Worker + moveToThread()方式
将线程对象(QThread)和工作对象(Worker)分离,工作逻辑在Worker的槽函数中实现。
-
工作对象(Worker)继承自QObject,包含一些槽函数,这些槽函数将在工作线程中执行。
-
创建一个QThread对象,并将Worker对象通过moveToThread()方法移动到新线程中。
-
通过信号触发Worker的槽函数,从而在Worker所在线程中执行任务。
-
这种方式将线程和任务分离,一个Worker对象可以在不同的线程中移动,并且一个线程可以供多个Worker对象使用(但同一时间只能有一个Worker对象在该线程中)。
继承QThread方式
class MyThread(QThread):
def init(self, ...):
super().init()
# 初始化def run(self): # 耗时操作 passWorker + moveToThread()方式
class Worker(QObject):
def init(self, ...):
super().init()
# 初始化@pyqtSlot() def do_work(self): # 耗时操作 pass使用方式
thread = QThread()
worker = Worker()
worker.moveToThread(thread)
thread.started.connect(worker.do_work)
thread.start()
信号与槽的连接
在两种方式中,都可以使用信号和槽进行通信,但连接方式有所不同。
继承QThread方式
信号可以定义在QThread子类中,然后在run()方法中发射。
注意:在run()方法中发射信号时,这些信号是在工作线程的上下文中发射的,因此连接到这个信号的槽函数将在接收者所在的线程中执行(由连接类型决定)。
Worker + moveToThread()方式
信号定义在Worker对象中,然后在Worker的槽函数中发射。
由于Worker对象被移动到了工作线程,所以它的槽函数会在工作线程中执行,而信号发射也是在Worker对象所在的线程中。
- 线程的生命周期管理
继承QThread方式
线程对象启动后,run()方法执行,直到返回,线程结束。
线程结束后,可以通过wait()等待线程结束,然后删除线程对象。
Worker + moveToThread()方式
线程对象启动后,通过信号触发Worker的槽函数,当槽函数执行完毕,线程的事件循环可以继续处理其他事件。
需要小心管理Worker和Thread的生命周期,通常的实践是:
当工作完成时,Worker发射一个finished信号,连接到线程的quit()槽,使线程退出事件循环。
将Worker的finished信号连接到Worker的deleteLater槽,以便在线程结束后删除Worker。
将线程的finished信号连接到线程的deleteLater槽,以便在线程结束后删除线程对象。 - 灵活性
继承QThread方式
每个线程实例只能执行一个任务(run方法中的任务),如果要执行不同的任务,需要定义不同的QThread子类。
如果要在同一个线程中执行多个任务,需要在run方法中组织多个任务,这可能会导致代码复杂。
Worker + moveToThread()方式
Worker对象可以定义多个槽函数,每个槽函数都可以是一个任务,并且这些槽函数都可以在同一个线程中执行(通过信号触发)。
同一个Worker对象可以移动到不同的线程,也可以有多个Worker对象在同一个线程中(但需要小心,因为一个对象只能在一个线程中)。 - 事件循环
继承QThread方式
默认情况下,QThread的run()方法启动一个事件循环(通过调用exec()),但如果我们重写了run()方法,并且没有调用exec(),那么该线程就没有事件循环。
没有事件循环,就不能在该线程中使用需要事件循环的机制(例如,定时器、网络通信等)。
Worker + moveToThread()方式
由于QThread的事件循环是默认启动的(run()方法中调用exec()),所以Worker对象可以使用需要事件循环的功能,例如,在Worker的槽函数中启动一个定时器,或者进行网络通信。 - 使用场景
继承QThread方式
适用于简单的后台任务,不需要事件循环,或者任务本身就是长时间运行的循环。
适合一次性任务,任务完成后线程就结束。
Worker + moveToThread()方式
适用于需要在后台执行多个任务,或者任务中需要使用事件循环(例如,定时器、网络通信)。
适合需要重复执行的任务,或者需要在线程中处理多个信号的任务。