一、QThread
https://doc.qt.io/qt-5/qthread.html https://doc.qt.io/qt-5/threads-synchronizing.html KDAB:https://www.youtube.com/watch?v=dcSsjxhazu0
QThread对象在程序中管理一个控制线程。QThreads在run()
中开始执行。默认情况下,run()
通过调用exec()
启动事件循环,并在线程中运行Qt事件循环。
可以通过使用QObject::moveToThread()
将worker对象移动到线程中来使用它们。
class Worker : public QObject
{
Q_OBJECT
public slots:
void doWork(const QString ¶meter) {
QString result;
/* ... here is the expensive or blocking operation ... */
emit resultReady(result);
}
signals:
void resultReady(const QString &result);
};
class Controller : public QObject
{
Q_OBJECT
QThread workerThread;
public:
Controller() {
Worker *worker = new Worker;
worker->moveToThread(&workerThread);
connect(&workerThread, &QThread::finished, worker, &QObject::deleteLater);
connect(this, &Controller::operate, worker, &Worker::doWork);
connect(worker, &Worker::resultReady, this, &Controller::handleResults);
workerThread.start();
}
~Controller() {
workerThread.quit();
workerThread.wait();
}
public slots:
void handleResults(const QString &);
signals:
void operate(const QString &);
};
Worker槽内的代码将在一个单独的线程中执行。但是,你可以自由地将Worker的插槽连接到任何线程中的任何对象的任何信号。由于一种称为排队连接的机制,跨不同线程连接信号和插槽是安全的。
class WorkerThread : public QThread
{
Q_OBJECT
void run() override {
QString result;
/* ... here is the expensive or blocking operation ... */
emit resultReady(result);
}
signals:
void resultReady(const QString &s);
};
void MyObject::startWorkInAThread()
{
WorkerThread *workerThread = new WorkerThread(this);
connect(workerThread, &WorkerThread::resultReady, this, &MyObject::handleResults);
connect(workerThread, &WorkerThread::finished, workerThread, &QObject::deleteLater);
workerThread->start();
}
在这个例子中,线程将在run函数返回后退出。除非调用exec(),否则线程中不会运行任何事件循环。
重要的是要记住,一个QThread实例存在于实例化它的旧线程中,而不是在调用run()
的新线程中。这意味着QThread的所有排队槽和调用的方法都将在旧线程中执行。因此,希望在新线程中调用插槽的开发人员必须使用worker-object
方法;新插槽不应该直接实现到QThread子类中。
与排队槽或调用方法不同,直接在QThread对象上调用的方法将在调用该方法的线程中执行。当子类化QThread时,请记住构造函数在旧线程中执行,而run()在新线程中执行。如果一个成员变量从两个函数访问,则该变量从两个不同的线程访问。检查这样做是否安全。
注意:当与不同线程的对象交互时必须小心。一般来说,函数只能从创建QThread对象本身的线程调用(例如setPriority()
),除非文档另有说明。请参见同步线程。
使代码在单独的线程中运行的另一种方法是将QThread子类化并重新实现run()。 例如:
class WorkerThread : public QThread
{
Q_OBJECT
void run() override {
QString result;
/* ... here is the expensive or blocking operation ... */
emit resultReady(result);
}
signals:
void resultReady(const QString &s);
};
void MyObject::startWorkInAThread()
{
WorkerThread *workerThread = new WorkerThread(this);
connect(workerThread, &WorkerThread::resultReady, this, &MyObject::handleResults);
connect(workerThread, &WorkerThread::finished, workerThread, &QObject::deleteLater);
workerThread->start();
}
在这个例子中,线程将在run函数返回后退出。除非调用exec()
,否则线程中不会运行任何事件循环。
重要的是要记住,一个QThread实例存在于实例化它的旧线程中,而不是在调用run()的新线程中。这意味着QThread的所有排队槽和调用的方法都将在旧线程中执行。因此,希望在新线程中调用插槽的开发人员必须使用worker-object方法;新插槽不应该直接实现到子类QThread中。
与排队槽或调用方法不同,直接在QThread对象上调用的方法将在调用该方法的线程中执行。当子类化QThread时,请记住构造函数在旧线程中执行,而run()在新线程中执行。如果一个成员变量从两个函数访问,则该变量从两个不同的线程访问。检查这样做是否安全。
注意:与跨不同线程的对象进行交互时必须小心。 通常,除非文档另有说明,否则只能从创建QThread对象本身的线程(例如setPriority()
)中调用函数。
管理线程
当线程的start()和finish()时,QThread会通过信号通知您,或者您可以使用isFinished()和isRunning()来查询线程的状态。
您可以通过调用exit()或quit()来停止线程。 在极端情况下,您可能需要强制终止执行线程。 但是,这样做是危险的,不鼓励这样做。 请阅读terminate()和setTerminationEnabled()的文档以获取详细信息。
从Qt 4.8开始,可以通过将finish()信号连接到QObject::deleteLater()释放生活在刚刚结束的线程中的对象。
使用wait()阻塞调用线程,直到另一个线程完成执行(或直到经过指定的时间)为止。
QThread还提供了与平台无关的静态睡眠功能:sleep(),msleep()和usleep()分别允许完整的秒,毫秒和微秒的分辨率。 这些功能已在Qt 5.0中公开。
注意:一般来说,因为Qt是一个事件驱动的框架,所以通常不需要使用wait()和sleep()函数。 可以考虑侦听finish()信号,而不是wait()。 考虑使用QTimer,而不是sleep()函数。
静态函数currentThreadId()和currentThread()返回当前正在执行的线程的标识符。 前者返回线程的特定于平台的ID。 后者返回一个QThread指针。
要选择将给您的线程提供的名称(例如,由Linux上的ps -L命令标识),您可以在启动线程之前调用setObjectName()
。如果你不调用setObjectName()
,给你的线程的名字将是你的线程对象的运行时类型的类名(例如,“RenderThread”在Mandelbrot例子的情况下,因为这是QThread子类的名字)。请注意,这目前在Windows上的发布版本中是不可用的。