Qt中的多线程及其应用(3)

七、信号与槽的连接方式

本节介绍信号与槽的五种连接方式:

Qt::DirectConnection(); //立即调用
Qt::QueuedConnection(); //异步调用
Qt::BlockingQueuedConnection(); //同步调用
Qt::AutoConnection(); //默认连接
Qt::UniqueConnection(); //单一连接

connection()函数的原型:


信号与槽的连接方式,决定了槽函数调用时候的相关行为。

知识回顾:每一个线程都有自己的事件队列,线程通过事件队列来接受信号,信号在事件循环中被处理。


编程示例:

代码和前面代码变化不大,主要是主函数。

void TestThread::run()
{
    qDebug() << "void TestThread::run() -- begin tid" << QThread::currentThreadId();

    for(int i=0; i<3; i++)
    {
        qDebug() << "void TestThread::run" << i;

        sleep(1);
    }

    emit testSignal();

    exec();

    qDebug() << "void TestThread::run() -- end " ;
}

在主函数中,通过不同的方式对信号和槽进行连接,代码如下:

#include <QCoreApplication>
#include <QDebug>
#include <QThread>
#include "TestThread.h"
#include "MyObject.h"

void direct_connection()
{
    static TestThread t;
    static MyObject m;

    QObject::connect(&t, SIGNAL(testSignal()), &m, SLOT(testSlot()), Qt::DirectConnection);
    //直接忽略线程依附性,被调用的对象,直接在调用者的线程中被执行。注意目标线程和当先线程必须不同

    qDebug() << " void direct_connection() ";

    t.start();

    t.wait(5 * 1000);

    t.quit();
}

void queued_connection()  //异步调用
{
    static TestThread t;
    static MyObject m;

    qDebug() << " void queued_connection() ";

    QObject::connect(&t, SIGNAL(testSignal()), &m, SLOT(testSlot()), Qt::QueuedConnection);
    //槽函数在主函数线程中被调用。  信号发送后,子线程run()先结束,进入主线程事件队列。

    t.start();

    t.wait(5 * 1000);

    t.quit();
}

void blocking_queued_connection()  //同步调用
{
    static TestThread t;
    static MyObject m;

    qDebug() << " void blocking_queued_connection()  ";

    QObject::connect(&t, SIGNAL(testSignal()), &m, SLOT(testSlot()), Qt::BlockingQueuedConnection);
    //不改变槽函数线程依附性,只是等待子线程(槽函数)结束后,继续向下执行。注:目标(执行)线程和发送信号的线程必须是不同的

    t.start();

    t.wait(5 * 1000);

    t.quit();
}

void auto_connection()  //默认连接
{
    static TestThread t;
    static MyObject m;

    QObject::connect(&t, SIGNAL(testSignal()), &m, SLOT(testSlot()), Qt::AutoConnection);
    QObject::connect(&t, SIGNAL(testSignal()), &m, SLOT(testSlot()), Qt::AutoConnection);
    QObject::connect(&t, SIGNAL(testSignal()), &m, SLOT(testSlot()), Qt::AutoConnection);
    //默认情况下,同一个信号可以多次连接到同一个槽函数,槽函数可以多次调用

    t.start();

    t.wait(5 * 1000);

    t.quit();
}

void unique_connection()  //单一连接
{
    static TestThread t;
    static MyObject m;

    qDebug() << "void unique_connection() ";

    QObject::connect(&t, SIGNAL(testSignal()), &m, SLOT(testSlot()), Qt::UniqueConnection);
    QObject::connect(&t, SIGNAL(testSignal()), &m, SLOT(testSlot()), Qt::UniqueConnection);
    //指定同一个信号于同一个槽函数之间只有一个连接

    t.start();

    t.wait(5 * 1000);

    t.quit();
}

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);

    qDebug() << "main() tid = " << QThread::currentThreadId();
 
 direct_connection();
// queued_connection(); //blocking_queued_connection(); //auto_connection(); //unique_connection(); return a.exec();}

运行结果如下:

立即调用:直接在发送信号的线程中调用槽函数,等价于槽函数的实时调用。直接忽略线程依附性,被调用的对象,直接在调用者的线程中被执行,注意目标线程和当前线程必须不同。


异步调用:信号发送至目标线程的事件队列,由目标线程进行处理,当前线程直接继续向下执行。槽函数在函数中被调用,信号发送后,子线程run()先结束,进入主线程事件队列。


同步调用:信号发送至目标线程的事件队列,由目标线程处理,当前线程等待槽函数返回,之后继续向下执行。不改变槽函数的线程依附性,只是等待子线程(槽函数)结束后,继续向下执行。注:目标(执行)线程和发送信号的线程必须是不同的。


默认连接:同一个信号可以多次连接到同一个槽函数,槽函数可以多次调用。



单一连接:功能与默认连接相同,自动确定连接类型,同一个信号与同一个槽函数之间只能有一个连接。

注:默认情况下,同一个信号可以多次连接到同一个槽函数,多次连接意味着同一个槽函数的多次调用。

上面代码中,信号与槽声明了两次,可是只执行了一次。


八、线程的生命期问题

C++对象有生命周期,线程也有生命周期,QThread对象的生命周期与对应的线程生命周期是否一致?

工程实践中的经验准则:线程对象的生命周期大于对应的线程的生命周期。

下面代码是否有问题:

void MyThread::run()
{
    this->i = 1;

    for(int i=0; i<5; i++)
    {
        this->i *= 2;

        sleep(1);
    }

}
void test()
{
    MyThread t;

    t.start();
}

局部对象脱离作用域以后自动销毁。所以在func()函数中,t.start()运行之后,run()还需要至少5秒钟才结束。

运行结果如下:


引入同步型线程设计概念:线程对象主动等待线程生命期结束后才销毁。

特点:同时直接在堆和栈中创建线程对象,对象销毁时确保线程生命期结束。

设计要点:在析构函数中调用wait()函数,强制等到线程运行结束。

适用场合:线程生命期较短的时候。

编程实现:

void SyncThread::run()
{
    qDebug() << "void SyncThread::run() tid = " << currentThreadId();

    for(int i=0; i<3; i++)
    {
        qDebug() << "void SyncThread::run() i = " << i;

        sleep(1);
    }

    qDebug() << "void SyncThread::run() end";
}

SyncThread::~SyncThread()
{
    wait();  //等待线程运行结束

    qDebug() << "SyncThread::~SyncThread() destroy thread object";
}

在析构函数中等待对象线程运行结束,运行结果如下:


异步型线程设计:

概念:线程生命期结束时,通知销毁线程对象。

特点:只能在中创建线程对象,线程对象不能被外界主动销毁。

设计要点:在run()函数中调用deleteLater()函数,线程体函数主动申请销毁线程对象。

适用场合:线程生命期不可控,需要长时间运行于后台的情形。

编程实现:

AsyncThread* AsyncThread::NewInstance(QObject *parent)
{
    return new AsyncThread(parent);
}

AsyncThread::AsyncThread(QObject *parent) :
    QThread(parent)
{
    
}

void AsyncThread::run()
{
    qDebug() << "void AsyncThread::run() tid = " << currentThreadId();

    for(int i=0; i<3; i++)
    {
        qDebug() << "void AsyncThread::run() i = " << i;

        sleep(1);
    }

    qDebug() << "void AsyncThread::run() end";

    deleteLater();
}

AsyncThread::~AsyncThread()
{
    qDebug() << "AsyncThread::~AsyncThread() destroy thread object";
}
void async_thread()
{
    AsyncThread* at = AsyncThread::NewInstance();

    at->start();
}

注意此处的二阶构造函数,以及对象是在堆上创建的。

运行结果如下:


总结:线程类的设计必须适应具体的场合,没有万能的设计,只有合适的设计。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值