最近做了个小项目,发现当数据库中数据量过大时,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.qml
import QtQuick
import QtQuick.Controls.Material
import mainthread 1.0
Window {
width: 640
height: 480
visible: true
title: qsTr("Hello World")
//ListModel先填写数据,保存后注释掉,再次打开验证读取
ListModel{
id:examplemodel
// ListElement{
// number:12
// name:"小明"
// major:"计算机"
// }
// ListElement{
// number:16
// name:"小李"
// major:"土木工程"
// }
}
ListView{
anchors.centerIn: parent
width: 290
height: 480
model:examplemodel
clip:true
spacing:40
delegate: Rectangle{
width: 290
height: 30
Row{
spacing: 50
Text {
text:model.number
anchors.verticalCenter: parent.verticalCenter
font.pixelSize: 20
}
Text {
text:model.name
anchors.verticalCenter: parent.verticalCenter
font.pixelSize: 20
}
Text {
text:model.major
anchors.verticalCenter: parent.verticalCenter
font.pixelSize: 20
}
}
}
Component.onCompleted: {
maintd.to_loaddata() //组件加载完毕时发出to_loaddata信号,请求子线程加载
}
}
Button{
anchors.right: parent.right
anchors.top: parent.top
height: 60
width: 100
text: "保存"
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:maintd
onDataready:{ //信号处理器,当MainThread发出dataready信号时,打印“保存成功”
console.log("保存成功")
}
onDataload: (data)=>{ //当MainThread发出dataload信号时,捕获data,更新examplemodel
examplemodel.clear()
for(let i=0;i<data.length;i++){
examplemodel.append(data[i])
}
}
}
}
简单来说,整体逻辑其实就是,在QML中发出信号,请求子线程处理任务,子线程完成操作后发出信号通知主线程,主线程调用槽函数接收数据并再次发出信号通知QML,QML通过信号处理器捕获信号并响应。
通过使用多线程,便能够实现先弹出加载动画,等到数据加载完毕后再显示等效果,从而提升界面流畅度。同时,多个线程也能够并行执行多个任务,有效的提高处理性能
709

被折叠的 条评论
为什么被折叠?



