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.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通过信号处理器捕获信号并响应。

通过使用多线程,便能够实现先弹出加载动画,等到数据加载完毕后再显示等效果,从而提升界面流畅度。同时,多个线程也能够并行执行多个任务,有效的提高处理性能

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值