Qt QML创建多线程(示例存读数据库)
最近做了个小项目,发现当数据库中数据量过大时,GUI界面就会卡死,因为耗时的读取操作会阻塞主线程。为了解决这个问题,我就使用了多线程来稍微重构了一下,用子线程来处理这类耗时任务,而让主线程负责绘制界面,从而保持界面响应。
那么如何创建一个子线程来与主线程进行数据交互呢,下面我来码个例子:
一、创建类
创建继承QObject的主线程类和子线程类,并声明槽函数和信号
//MainThread.h#ifndef MAINTHREAD_H
#define MAINTHREAD_H#include <QObject>
#include "multithread.h"
#include <QThread>class MainThread : public QObject
{Q_OBJECT
public:explicit MainThread(QObject *parent = nullptr);~MainThread(); //析构函数,当程序退出时自动调用private slots:void getdata(); //释放dataready信号void loaddata(const QVariantList &data); //释放dataload信号signals:void to_getdata(const QVariantList &data); //通知子线程去准备数据void to_loaddata(); //通知子线程去加载数据void dataready(); //子线程数据准备完毕void dataload(const QVariantList &data); //子线程数据加载完毕private:MultiThread *worker; //工作类对象QThread *workthread; //子线程对象
};#endif // MAINTHREAD_H
//MultiThread.h#ifndef MULTITHREAD_H
#define MULTITHREAD_H#include <QObject>
#include <QSqlDatabase>class MultiThread : public QObject
{Q_OBJECT
public:explicit MultiThread(QObject *parent = nullptr);~MultiThread();public slots:void do_getdata(const QVariantList &data); //处理保存数据void do_loaddata(); //处理读取数据signals:void dataready(const QVariantList &data); //数据保存结束void datachange(); //数据读取结束private:void init(); //初始化创建QSqlDatabase db;
};#endif // MULTITHREAD_H
二、实现子线程槽函数
我们先实现子线程中实际处理数据的槽函数,例子中就是实现了一下数据库的存读操作,并在操作结束后发出信号来通知主线程
//MultiThread.cpp#include "multithread.h"
#include <QSqlQuery>
#include <QSqlDatabase>
#include <QSqlError>MultiThread::MultiThread(QObject *parent): QObject{parent}
{init();
}MultiThread::~MultiThread()
{if(db.open()){db.close();}
}void MultiThread::do_getdata(const QVariantList &data)
{QSqlQuery cleardata(db);cleardata.exec("DELETE FROM sample");QSqlQuery insertdata(db);insertdata.prepare("INSERT INTO sample (number,name,major)""VALUES (?,?,?)");for(const QVariant &piece:data){QVariantMap map;map=piece.toMap();insertdata.addBindValue(map["number"]);insertdata.addBindValue(map["name"]);insertdata.addBindValue(map["major"]);if(!insertdata.exec()){qWarning()<<"子线程添加数据失败"<<insertdata.lastError().text();}}emit datachange(); //发出保存数据操作结束的信号qDebug()<<"子线程保存成功";
}void MultiThread::do_loaddata()
{QVariantList list;QSqlQuery query(db);query.exec("SELECT * FROM sample");while(query.next()){QVariantMap map;map["number"]=query.value(0).toInt();map["name"]=query.value(1).toString();map["major"]=query.value(2).toString();list.append(map);}qDebug()<<"子线程读取成功";emit dataready(list); //发出读取数据操作结束的信号
}void MultiThread::init()
{if(QSqlDatabase::contains("example")){db=QSqlDatabase::database("example");}else{db=QSqlDatabase::addDatabase("QSQLITE","example");}db.setDatabaseName("sample.db");if(!db.open()){qDebug()<<"子线程打开数据库失败"<<db.lastError().text();}QSqlQuery query(db);query.prepare("CREATE TABLE IF NOT EXISTS sample(""number INTEGER PRIMARY KEY,""name TEXT,""major TEXT"")");if(!query.exec()){qWarning()<<"子线程创建数据库失败"<<query.lastError().text();}
}
三、在主线程类中实现信号与槽的连接
用信号与槽机制实现主线程和子线程的交互,并发出信号通知GUI界面更新操作
//MainThread.cpp#include "mainthread.h"
#include <QThread>MainThread::MainThread(QObject *parent): QObject{parent}
{workthread=new QThread(this); //创建子线程,当主线程销毁时worker=new MultiThread(); //创建工作类的实例worker->moveToThread(workthread); //将其移动至子线程//线程管理,防止内存泄漏connect(workthread,&QThread::finished,worker,&QObject::deleteLater); //当子线程结束时,调用worker->deleteLater(),即自动销毁工作类对象connect(workthread,&QThread::finished,workthread,&QObject::deleteLater); //同上,当子线程结束时,销毁子线程本身//存读操作connect(this,&MainThread::to_getdata,worker,&MultiThread::do_getdata); //存储操作//当MainThread对象发出to_getdata信号时,worker会在子线程接收并调用do_getdata槽函数connect(worker,&MultiThread::datachange,this,&MainThread::getdata); //当woker完成do_getdata后发出datachange信号,MainThread对象调用getdata槽函数发出dataready信号,通知GUI界面加载数据connect(this,&MainThread::to_loaddata,worker,&MultiThread::do_loaddata); //读取操作connect(worker,&MultiThread::dataready,this,&MainThread::loaddata);workthread->start(); //启动子线程
}MainThread::~MainThread()
{workthread->quit(); //发出退出请求workthread->wait(); //阻塞调用线程,确保子线程退出
}void MainThread::getdata()
{emit dataready();
}void MainThread::loaddata(const QVariantList &data)
{emit dataload(data);
}
四、QML示例
我在main.qml中写了个ListView来演示了一下存读操作
//main.qmlimport QtQuick
import QtQuick.Controls.Material
import mainthread 1.0Window {width: 640height: 480visible: truetitle: qsTr("Hello World")//ListModel先填写数据,保存后注释掉,再次打开验证读取ListModel{id:examplemodel// ListElement{// number:12// name:"小明"// major:"计算机"// }// ListElement{// number:16// name:"小李"// major:"土木工程"// }}ListView{anchors.centerIn: parentwidth: 290height: 480model:examplemodelclip:truespacing:40delegate: Rectangle{width: 290height: 30Row{spacing: 50Text {text:model.numberanchors.verticalCenter: parent.verticalCenterfont.pixelSize: 20}Text {text:model.nameanchors.verticalCenter: parent.verticalCenterfont.pixelSize: 20}Text {text:model.majoranchors.verticalCenter: parent.verticalCenterfont.pixelSize: 20}}}Component.onCompleted: {maintd.to_loaddata() //组件加载完毕时发出to_loaddata信号,请求子线程加载}}Button{anchors.right: parent.rightanchors.top: parent.topheight: 60width: 100text: "保存"onClicked: {let data=[]for(let i=0;i<examplemodel.count;i++){let getdata=examplemodel.get(i)data.push({number:getdata.number,name:getdata.name,major:getdata.major})}maintd.to_getdata(data) //点击保存发出to_getdata信号,请求子线程保存}}MainThread{id:maintdonDataready:{ //信号处理器,当MainThread发出dataready信号时,打印“保存成功”console.log("保存成功")}onDataload: (data)=>{ //当MainThread发出dataload信号时,捕获data,更新examplemodelexamplemodel.clear()for(let i=0;i<data.length;i++){examplemodel.append(data[i])}}}
}
简单来说,整体逻辑其实就是,在QML中发出信号,请求子线程处理任务,子线程完成操作后发出信号通知主线程,主线程调用槽函数接收数据并再次发出信号通知QML,QML通过信号处理器捕获信号并响应。
通过使用多线程,便能够实现先弹出加载动画,等到数据加载完毕后再显示等效果,从而提升界面流畅度。同时,多个线程也能够并行执行多个任务,有效的提高处理性能