QT异步线程通信
在使用 QThreadPool
提交任务后,如果你需要知道任务何时完成,并且需要使用任务的执行结果,可以通过以下几种方式来实现:
1. 使用信号和槽
QRunnable
提供了一个 finished()
信号,当任务执行完成后会发出。你可以在任务完成后通过信号和槽机制通知主线程。
示例代码
#include <QCoreApplication>
#include <QThreadPool>
#include <QRunnable>
#include <QDebug>
#include <QThread>class Worker : public QRunnable {
public:Worker() {// 连接 finished 信号到自定义槽connect(this, &Worker::finished, this, &Worker::onFinished);}void run() override {qDebug() << "Worker running in thread" << QThread::currentThreadId();// 模拟耗时任务QThread::sleep(3);qDebug() << "Worker finished";emit finished(); // 发出任务完成信号}private slots:void onFinished() {qDebug() << "Worker finished in thread" << QThread::currentThreadId();// 在这里可以处理任务完成后的逻辑}
};int main(int argc, char *argv[]) {QCoreApplication app(argc, argv);// 获取全局线程池QThreadPool* globalThreadPool = QThreadPool::globalInstance();// 创建一个任务Worker* worker = new Worker();// 将任务添加到全局线程池qDebug() << "Starting worker in main thread" << QThread::currentThreadId();globalThreadPool->start(worker);// 主线程继续运行qDebug() << "Main thread continues running immediately";QThread::sleep(1); // 等待一段时间,观察输出qDebug() << "Main thread still running";return app.exec();
}
输出示例
Starting worker in main thread 0x1234
Main thread continues running immediately
Main thread still running
Worker running in thread 0x5678
Worker finished
Worker finished in thread 0x5678
2. 使用 QFuture
和 QtConcurrent::run
如果你需要更灵活的异步任务管理,可以使用 QtConcurrent::run
,它会返回一个 QFuture
对象,你可以通过它来检查任务的状态或获取任务的返回值。
示例代码
#include <QCoreApplication>
#include <QtConcurrent>
#include <QDebug>
#include <QThread>
#include <QFuture>
#include <QFutureWatcher>int workerFunction() {qDebug() << "Worker running in thread" << QThread::currentThreadId();// 模拟耗时任务QThread::sleep(3);qDebug() << "Worker finished";return 42; // 返回结果
}int main(int argc, char *argv[]) {QCoreApplication app(argc, argv);// 使用 QtConcurrent::run 提交任务QFuture<int> future = QtConcurrent::run(workerFunction);// 主线程继续运行qDebug() << "Main thread continues running immediately";QThread::sleep(1); // 等待一段时间,观察输出qDebug() << "Main thread still running";// 等待任务完成并获取结果qDebug() << "Waiting for worker to finish...";int result = future.result(); // 阻塞主线程,直到任务完成qDebug() << "Worker result:" << result;return app.exec();
}
输出示例
Main thread continues running immediately
Main thread still running
Worker running in thread 0x5678
Worker finished
Waiting for worker to finish...
Worker result: 42
3. 使用 QThreadPool::waitForDone()
如果你需要等待所有任务完成,可以使用 QThreadPool::waitForDone()
方法。这个方法会阻塞当前线程,直到线程池中的所有任务都完成。
示例代码
#include <QCoreApplication>
#include <QThreadPool>
#include <QRunnable>
#include <QDebug>
#include <QThread>class Worker : public QRunnable {
public:void run() override {qDebug() << "Worker running in thread" << QThread::currentThreadId();// 模拟耗时任务QThread::sleep(3);qDebug() << "Worker finished";}
};int main(int argc, char *argv[]) {QCoreApplication app(argc, argv);// 获取全局线程池QThreadPool* globalThreadPool = QThreadPool::globalInstance();// 创建一个任务Worker* worker = new Worker();// 将任务添加到全局线程池qDebug() << "Starting worker in main thread" << QThread::currentThreadId();globalThreadPool->start(worker);// 主线程继续运行qDebug() << "Main thread continues running immediately";QThread::sleep(1); // 等待一段时间,观察输出qDebug() << "Main thread still running";// 等待所有任务完成qDebug() << "Waiting for all tasks to finish...";globalThreadPool->waitForDone();qDebug() << "All tasks finished";return app.exec();
}
输出示例
Starting worker in main thread 0x1234
Main thread continues running immediately
Main thread still running
Worker running in thread 0x5678
Worker finished
Waiting for all tasks to finish...
All tasks finished
总结
- 信号和槽:通过
QRunnable::finished
信号通知任务完成。 QFuture
和QtConcurrent::run
:提供更灵活的异步任务管理,可以获取任务的返回值。QThreadPool::waitForDone
:等待线程池中的所有任务完成,但会阻塞当前线程。
根据你的需求选择合适的方法。如果你需要任务的返回值,建议使用 QtConcurrent::run
和 QFuture
。
QtConcurrent::run
和直接使用 QThread
在功能和使用方式上有显著的区别。以下是它们的主要区别:
1. 使用方式
-
QtConcurrent::run
:- 更简单:
QtConcurrent::run
是一个高级接口,用于简化异步任务的提交和管理。它返回一个QFuture
对象,可以用来检查任务的状态或获取任务的返回值。 - 无需手动管理线程:你只需要提供一个函数或 lambda 表达式,
QtConcurrent::run
会自动将任务提交到线程池中执行,无需手动创建和管理QThread
。 - 支持返回值:
QFuture
可以存储任务的返回值,方便在任务完成后获取结果。
- 更简单:
-
QThread
:- 更灵活:
QThread
是一个低级接口,提供了对线程的细粒度控制。你可以创建自己的线程类,管理线程的启动、停止和同步。 - 需要手动管理线程:你需要手动创建线程,连接信号和槽,管理线程的生命周期。
- 不直接支持返回值:线程的执行结果需要通过信号和槽或其他机制传递回主线程。
- 更灵活:
2. 线程管理
-
QtConcurrent::run
:- 使用线程池:
QtConcurrent::run
内部使用QThreadPool
来管理线程。任务会被提交到全局线程池中,由线程池负责分配线程。这种方式可以减少线程创建和销毁的开销,提高性能。 - 自动管理线程生命周期:任务完成后,线程会自动返回线程池,无需手动管理。
- 使用线程池:
-
QThread
:- 手动管理线程:你需要手动创建和启动线程,并在任务完成后手动停止线程。
- 线程生命周期:线程的生命周期由你控制,需要确保线程在任务完成后正确退出。
3. 任务状态和结果
-
QtConcurrent::run
:QFuture
提供状态检查:QFuture
提供了多种方法来检查任务的状态,例如:isFinished()
:检查任务是否完成。isRunning()
:检查任务是否正在运行。result()
:获取任务的返回值。
- 支持异步操作:
QFuture
可以与QFutureWatcher
配合使用,通过信号和槽机制在任务完成时通知主线程。
-
QThread
:- 手动检查状态:你需要通过信号和槽机制或手动检查线程的状态。
- 不直接支持返回值:线程的执行结果需要通过信号和槽或其他机制传递回主线程。
4. 适用场景
-
QtConcurrent::run
:- 简单任务:适用于简单的异步任务,特别是那些不需要复杂线程管理的场景。
- 任务结果处理:当你需要获取任务的返回值时,
QFuture
提供了方便的接口。
-
QThread
:- 复杂任务:适用于需要更细粒度控制线程的复杂任务。
- 长时间运行的任务:适用于需要长时间运行的后台任务,例如网络通信、文件处理等。
示例对比
使用 QtConcurrent::run
#include <QCoreApplication>
#include <QtConcurrent>
#include <QDebug>
#include <QThread>int workerFunction() {qDebug() << "Worker running in thread" << QThread::currentThreadId();QThread::sleep(3);qDebug() << "Worker finished";return 42; // 返回结果
}int main(int argc, char *argv[]) {QCoreApplication app(argc, argv);// 提交任务QFuture<int> future = QtConcurrent::run(workerFunction);// 主线程继续运行qDebug() << "Main thread continues running immediately";QThread::sleep(1);// 等待任务完成并获取结果qDebug() << "Waiting for worker to finish...";int result = future.result();qDebug() << "Worker result:" << result;return app.exec();
}
使用 QThread
#include <QCoreApplication>
#include <QThread>
#include <QDebug>
#include <QMutex>
#include <QWaitCondition>class Worker : public QObject {Q_OBJECT
public:Worker() : result(0), finished(false) {}void run() {qDebug() << "Worker running in thread" << QThread::currentThreadId();QThread::sleep(3);result = 42; // 设置结果finished = true; // 标记任务完成condition.wakeOne(); // 通知主线程}int getResult() const { return result; }bool isFinished() const { return finished; }signals:void finished();private:mutable QMutex mutex;QWaitCondition condition;int result;bool finished;
};int main(int argc, char *argv[]) {QCoreApplication app(argc, argv);Worker worker;QThread thread;worker.moveToThread(&thread);QObject::connect(&thread, &QThread::started, &worker, &Worker::run);thread.start();qDebug() << "Main thread continues running immediately";QThread::sleep(1);// 等待任务完成qDebug() << "Waiting for worker to finish...";QMutexLocker locker(&worker.mutex);while (!worker.isFinished()) {worker.condition.wait(&locker);}int result = worker.getResult();qDebug() << "Worker result:" << result;thread.quit();thread.wait();return app.exec();
}
总结
-
QtConcurrent::run
:- 优点:简单易用,自动管理线程,支持返回值。
- 缺点:功能相对有限,适合简单任务。
- 适用场景:适合简单的异步任务,特别是需要获取任务结果的场景。
-
QThread
:- 优点:功能强大,支持复杂的线程管理。
- 缺点:使用复杂,需要手动管理线程。
- 适用场景:适合需要细粒度控制线程的复杂任务。
根据你的需求选择合适的方式。如果你的任务简单且需要返回值,推荐使用 QtConcurrent::run
。如果你的任务复杂且需要更细粒度的线程管理,推荐使用 QThread
。
在 Qt 中,QFuture
是一个线程安全的对象,用于表示异步操作的结果。当你将 QFuture
作为值传递给其他函数时,实际上传递的是一个轻量级的“未来”对象的副本。这个副本与原始的 QFuture
对象共享底层的异步操作状态。
关键点
- 共享状态:尽管
QFuture
是按值传递的,但它内部维护的是一个共享的状态。这意味着,无论你传递了多少个副本,它们都会指向同一个底层的异步操作状态。 - 线程安全:
QFuture
的状态是线程安全的,因此你可以在多个线程中安全地访问和修改它的状态。
示例
假设你有以下代码:
void MachineFileBrowser::handleSingleClick(const QModelIndex &index)
{if (index.isValid() && lastClickedIndex == index){QString path = model->getFilePath(index);fileloadFuture = QtConcurrent::run([this, path]() {MachineTree &tempTree = currentTemplate->getMachineTree();tempTree = MachineTree::parseFromTarXmlFile(path); // 解析机器树数据});emit itemReClicked(index, fileloadFuture);} else{lastClickedIndex = index;}
}
在 itemReClicked
信号的槽函数中,你可以这样处理:
void SomeClass::handleItemReClicked(const QModelIndex &index, QFuture<void> future)
{// 检查任务是否完成if (future.isFinished()){qDebug() << "Task is finished";}else{qDebug() << "Task is still running";}
}
关键点解释
-
fileloadFuture
的状态:fileloadFuture
是一个QFuture<void>
对象,它表示一个异步操作的未来结果。- 当你将
fileloadFuture
传递给itemReClicked
信号时,传递的是一个副本,但这个副本与原始的fileloadFuture
共享底层的状态。
-
状态同步:
- 无论你在哪个地方访问
fileloadFuture
,它的状态(例如isFinished()
)始终是同步的。这是因为QFuture
内部使用了共享的状态机制。 - 这意味着,即使你在多个地方持有
fileloadFuture
的副本,它们的状态始终是一致的。
- 无论你在哪个地方访问
示例代码
假设你有一个槽函数 handleItemReClicked
,它接收 fileloadFuture
的副本:
void SomeClass::handleItemReClicked(const QModelIndex &index, QFuture<void> future)
{// 检查任务是否完成if (future.isFinished()){qDebug() << "Task is finished";}else{qDebug() << "Task is still running";}
}
在 handleSingleClick
中,你发射了 itemReClicked
信号:
emit itemReClicked(index, fileloadFuture);
在槽函数中,你可以通过 future.isFinished()
检查任务是否完成。无论任务是否完成,future.isFinished()
的结果始终是正确的,因为 QFuture
的状态是共享的。
总结
QFuture
的状态是共享的:即使你将QFuture
作为值传递,它的状态仍然是共享的。- 线程安全:
QFuture
的状态是线程安全的,你可以在多个线程中安全地访问和修改它的状态。 - 状态同步:无论你在哪个地方访问
QFuture
,它的状态始终是一致的。
因此,即使 fileloadFuture
是按值传递的,你仍然可以在其他函数中通过 isFinished()
检查任务的状态,并且结果始终是正确的。