采用lambda简化QThread和UI同步

用惯了C#和JAVA,再过来用QT发现很多不便。不能说谁优谁略,起码有些地方可以相互借鉴和互补的,例如线程的使用。

在C#上有Thread,有Task可以很方便创建后台任务线程,在JAVA上有Thread和Runnable,但在QT上相对麻烦一些,或者说至少代码逻辑没有那么的流畅。但也或许是个人习惯或功夫不到家造成的不违和感吧。不管怎么样,下面尝试在QT上写出类似C#的写法。

线程的使用多用于后台任务和UI同步,要在QT上写出类似C#的方式先要搞清楚C#是怎么实现多线程和UI同步的。

1. C#中的线程和UI同步

在C#中可以通过new Thread(Action)创建一个线程,然后调用Start方法启动线程。创建线程使用的Action是一个委托,在c++中可以认为是一个函数指针,并且是一个没有带参数和返回结果的函数指针。
线程创建之后可以通过UI对象的Invoke(Action),在Action中对UI进行更新。

而在C#中Action可以采用lambda,所以在代码编写多数采用这种写法。具体看下面代码。

			//创建线程
            var thread = new Thread(() => {
                //线程同步
                this.Invoke(new Action(() => {
                    this.Text = "在子线程中采用Invoke更新UI窗口";
                }));
            });
            //开始线程
            thread.Start();

C#中的UI同步机制是消息,包括象c++的MFC,C#的winform,WPF等都是采用这种消息机制,再到Android中的Looper也是类似这种方式。

这种机制简单的理解就是UI线程是一个循环体,不断从一个消息队列里取消息,并执行这条消息。而消息的表现方式是多样化,规则化,系统化的。比如鼠标点击,键盘的按下等都可以用消息表达。

随手牵来一张图片说明消息机制

在这里插入图片描述

2. QT中是怎么实现多线程和UI同步的

QT中常用有两种多线程实现的写法。一种是定义一个QThread的子类,在子类中实现任务处理逻辑。一种是定义一个QObject子类,在子类中实现任务处理逻辑,同时在使用的时候创建一个QThread,并将子类moveToThread到QThread中执行。
具体可以参考https://subingwen.cn/qt/thread/

QT中的UI同步则采用信号和槽的机制,跟C#的winform,vc++的mfc是类似的。具体就不细说了。

3. 如何进行简化

搞清楚C#和QT的线程实现方式之后能发现一个明显的区别就是采用了lambda,lambda在是c11才列入标准,而QT在c11之前很早就诞生了,个人理解是QT一直沿用原来的模式。因此我想着采用lambda进行简化。

具体看下面代码,为了方便演示,将函数体也在头文件中,
(这里分享个QTCreator的一个小技巧 右键函数声明或类声明 菜单 Refactor->Move Definition …可以对单个函数体或所有类的函数体在.h和.cpp中自动切换)

自定义函数


//定义函数结构,这里类似C#的Action.
typedef std::function<void ()> FUNC;

//定义自定义线程类,从QThread派生
class MyThread:public QThread{
    Q_OBJECT
private:
    FUNC m_func=0;//记录线程执行的函数
public:
    //构造函数,func参数为线程执行的函数
    explicit MyThread(FUNC func=NULL, QObject *parent = Q_NULLPTR):QThread(parent),
        m_func(func)
    {
        //链接信号和槽,在里是子线程向UI同步的关键。
        connect(this,&MyThread::invokeSignal,this,&MyThread::invokeSlot);
    }

    ~MyThread()
    {
        //断开信号槽
        disconnect(this,&MyThread::invokeSignal,this,&MyThread::invokeSlot);
    }

    //设置线程执行的函数,虽然构造函数里面已经有func,但在有些场景在lambda中需要访问MyThread,所以需要该函数
    //该函数需要在start()调用
    void setFunc(FUNC func){
        this->m_func=func;
    }

    //同步到UI线程调用函数
    void invoke(FUNC func){
        emit invokeSignal(func);
    }

protected:
    //重写父函数,单线程开始执行会调用该函数,可以简单的认为是线程的入口。
    void run() override{
        //调用函数体,执行任务
        m_func();
    }

signals:
    //UI同步的信号
    void invokeSignal(FUNC fun);

public slots:
    //UI同步的槽函数,该函数一般为同步到UI线程来调用
    void invokeSlot(FUNC fun){
        //调用UI同步函数。
        fun();
    }


};


测试代码


MainWindow::MainWindow(QWidget *parent) :
    QMainWindow(parent),
    ui(new Ui::MainWindow)
{

    ui->setupUi(this);

    //创建一按钮用于测试
    QPushButton * btn=new QPushButton(this);
    btn->setFixedSize(100,30);
    btn->move(100,100);
    btn->show();

    //注册匿名函数类型
    qRegisterMetaType<FUNC>("FUNC");

    //输出UI线程ID
    qDebug()<<"UIThread ID:"<<QThread::currentThreadId();

    //新建一个线程
    MyThread * th0=new MyThread();

    //子线程任务逻辑,打印数字,更新UI
    auto func=[=](){
        int i=0;
        while(i++<20){
            qDebug()<<"sub thread id:"<< QThread::currentThreadId() <<"  i:"<<i<<"  tid:"<<QThread::currentThreadId();
            QThread::msleep(500);

            //同步到UI线程更新UI,因为这里用到th0,所以不能在MyThread的构造函数中定义
            th0->invoke([=](){
                 btn->setText(QString::number(i));
                 btn->move(i, 100);
            });
        }
    };
    //设置任务函数
    th0->setFunc(func);
    //开始线程执行
    th0->start();

    //在窗体结束的时候停止线程
    connect(this,&MainWindow::destroyed,[=](){
        //结束线程,这里有时候不会生效,下次再对这个问题进行解释。
        th0->exit(0);
        th0->wait(3000);
    });

}

4. 后续完善
以上实现会有几个问题,例如窗口释放了但线程还在运行,还需要UI同步就会异常、在某些时候如窗口关闭的时候需要停止线程无法停止等。因此需要有状态更新和通知的机制。今天就先写到这,下回有时间再补充。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值