QThread与QWidget使用[通俗易懂]

QThread与QWidget使用[通俗易懂] 原文链接:http://hi.baidu.com/cyclone/blog/item/65f3f603294f2e783812bb51.html注意:请优先考虑Qt线程基础(QThread、QtConcurrent等)dbzhang8002011.06.18 本文主要内容: 在任务一中,用四种方式实现:点击界面按钮,开线程运行一段程序,结果显示在一个La…

大家好,又见面了,我是你们的朋友全栈君。

 

原文链接:http://hi.baidu.com/cyclone/blog/item/65f3f603294f2e783812bb51.html

注意:请优先考虑Qt 线程基础(QThread、QtConcurrent等)

dbzhang800 2011.06.18

 

本文主要内容:

 

在任务一中,用 四 种方式实现:
点击界面按钮,开线程运行一段程序,结果显示在一个Label上。
1. 用不正确的方式得到看似正确的结果

2. 用Qt Manual 和 例子中使用的方法

3. 用一种好用但被Qt开发人员批判的方法

4. 用一种被开发人员强烈推荐,但Qt Manual和例子中只字未提的方法

  • 为了简单起见,本文只讲如何做及其结果是什么,而不讲其原因是什么(估计大家对原因也不会感兴趣,详见: QThread 使用探讨  和 QThread使用方法)。

  • 本文只考虑两个线程(即主线程和一个次线程)的情况。

QWidget

  • QWidget及其派生类均 不能在次线程中使用或创建

Manual 中的原话:

  • The GUI classes, notably QWidget and all its subclasses, are not reentrant. They can only be used from the main thread.
  • 因为不允许,所以尝试这么做的,几乎很快都能回头。毕竟signals和slots用起来确实蛮方便
  • 但是,回头后,就理解和用对 QThread 了么?

QThread

概念一:QThread 对象本身所依附的线程 和它管理的线程不是同一个线程。

  • 前者是主线程
  • 后者是次线程

概念二:你在QThread派生类中定义的槽在主线程而不是在次线程中执行的。

  • run 函数是线程的入口点,run内的代码才是在次线程中运行的代码

概念三:除了Manual和Qt例子中给出的用法外,QThread有一种更容易且被推荐的使用方法:

  • QThread 应该被看做是操作系统线程的接口或控制点,而不应该包含需要在新线程中运行的代码
  • 需要运行的代码应该放到一个QObject的子类中,然后将该子类的对象moveToThread到新线程中。

关于本文的例子

  • 为了代码简单,所有例子都是单一的源文件,保存为 main.cpp
    • 你从代码中包含的 #include“main.moc”应该能看出

  • 为了省几行代码,头文件都是直接包含 QtCore 和 QtGui。

  • 为了清楚告诉大家槽函数分别是在那个线程运行的,调用了几处 currentThreadId 函数
    • main 函数中输出主线程IDqDebug()<<"main: "<<QThread::currentThreadId();

    • run 函数中输出次线程IDqDebug()<<"thread: "<<currentThreadId();

    • 槽函数中输出其在哪个线程中执行qDebug()<<"slots1: "<<currentThreadId();

  • 因为用了qDebug,所以你的pro文件内最好加上 CONFIG+=console

  • 同样为了省代码,例子中未考虑线程如何正常结束的问题。

任务一

点击界面按钮,开线程运行一段程序,结果显示在一个Label上。

  • 定义一个Widget,上面放置 QPushButton 和 QLabel
  • 定义一个Thread,执行我们的代码,然后通知 Widget

第一次尝试

很容易想到方法,代码可以工作,结果正确。但 … 未必和你想得一样

  • Thread 中定义一个slot1函数,接受数据,计算其立方,然后将结果通过信号发出
  • Widget 中按钮每点击一次,发出的数据加1,用label接受Thread的信号

 

#include <QtCore> 
#include <QtGui> 
 
class Thread:public QThread 
{ 
    Q_OBJECT 
public: 
    Thread(){} 
public slots: 
    void slot1(int v) 
    { 
        qDebug()<<"slots1: "<<currentThreadId(); 
        emit sig1(QString::number(v*v*v)); 
    } 
signals: 
    void sig1(const QString& t); 
protected: 
    void run() 
    { 
        qDebug()<<"thread: "<<currentThreadId(); 
        exec(); 
    } 
}; 
 
class Widget:public QWidget 
{ 
    Q_OBJECT 
public: 
    Widget():m_label(new QLabel), m_button(new QPushButton("Button")), m_thread(new Thread) 
    { 
        QVBoxLayout * vbox = new QVBoxLayout(this); 
        vbox->addWidget(m_label); 
        vbox->addWidget(m_button); 
        setLayout(vbox); 
         connect(m_button,SIGNAL(clicked()),this,SLOT(onButtonClicked())); 
        connect(this,SIGNAL(clicked(int)),m_thread,SLOT(slot1(int))); 
        connect(m_thread,SIGNAL(sig1(QString)),m_label,SLOT(setText(QString))); 
        m_thread->start(); 
    } 
signals: 
    void clicked(int v); 
private slots: 
    void onButtonClicked() 
    { 
        static int v = 0; 
        emit clicked(v); 
        v++; 
    } 
private: 
    QLabel * m_label; 
    QPushButton * m_button; 
    Thread * m_thread; 
}; 
 
#include "main.moc" 
int main(int argc, char** argv) 
{ 
    QApplication app(argc, argv); 
    qDebug()<<"main: "<<QThread::currentThreadId(); 
    Widget w; 
    w.show(); 
    return app.exec(); 
}

一切工作正常,但看看控制台输出呢?

main:  3055777552 
thread:  3024481136  
slots1:  3055777552  
slots1:  3055777552  
slots1:  3055777552  
...

这儿明确告诉你,slot1 是在主线程中执行的。

尝试二

我们试试 Qt Manual和 Qt 例子中采用的解决方案。

槽函数不是在主线程运行么,而run函数不是次线程么?那么我们就:

  • 在槽函数中做个标记
  • 在run函数中根据标记进行运行

这样以来,尽管槽函数在仍在主线程,但费时的计算代码都在次线程了。

对Thread的类的改造如下(程序其他部分和 尝试一 完全一样):

 

class Thread:public QThread 
{ 
    Q_OBJECT public: 
    Thread(){} 
public slots: 
    void slot1(int v) 
    { 
        qDebug()<<"slots1: "<<currentThreadId(); 
        m_mutex.lock(); 
        m_vals.enqueue(v); 
        m_mutex.unlock(); 
    } 
signals: 
    void sig1(const QString& t); 
protected: 
    void run() 
    { 
        qDebug()<<"thread: "<<currentThreadId(); 
        while(1) { 
            m_mutex.lock(); 
            if (!m_vals.isEmpty()){ 
                int v = m_vals.dequeue(); 
                emit sig1(QString::number(v*v*v)); 
            } 
            m_mutex.unlock(); 
        } 
    } 
private: 
    QQueue<int> m_vals; 
    QMutex m_mutex; 
};

注意哦,因为 slot 函数在主线程中,而run函数在次线程中,所以二者需要 QMutex 实现对变量的安全访问。如果你认真看过Qt自带的例子,会发现它始终强调 QMutex 的使用。

尝试三

尝试二是”正统”的做法,但如过你用Google搜索过。那么你可能不会选择尝试二,而是会使用下面的方法(其他部分和尝试一 完全一样)

class Thread:public QThread 
{ 
    Q_OBJECT public: 
    Thread(){ moveToThread(this); } 
...

这样以来,slot函数确实是在次线程工作的,看看控制台输出

main:  3056785168  
thread:  3024444272  
slots1:  3024444272  
slots1:  3024444272  
...

很有意思?不是么,一条 moveToThread(this),移动到自己。然后问题解决了。

  • 因为前面说了,QThread 所依附线程 和 它管理的线程不是同一个。
  • 这样,其实将自己所依附的线程改为自己所管理的线程了。

o(∩∩)o…哈哈,不要太高兴哦,这个方法看起来比较舒服,但是它是被官方人员强烈批判的用法

尝试四

终于到我想写的代码了,这是Qt线程的开发者建议的使用方式,但很可惜。直到目前(Qt4.7.0),手册和例子中对此都只字为提。

  • 我们不子类话QThread了,我们只需要子类话一个QObject,然后将其move到QThread就行了,看代码:
  • 不用子类化QThrad了,我们只需要子类话一个 QObject,需要在次线程中工作的代码,直接放到它的槽中

 

class Worker:public QObject 
{ 
    Q_OBJECT 
public: 
    Worker(){} 
public slots: 
    void slot1(int v) 
    { 
        qDebug()<<"slots1: "<<QThread::currentThreadId(); 
        emit sig1(QString::number(v*v*v)); 
    } 
signals: 
    void sig1(const QString& t); 
};
  • 因为没有Thread类了,只有Worker类,Widget代码需做点改动

 

class Widget:public QWidget 
{ 
    Q_OBJECT public: 
    Widget():m_label(new QLabel), m_button(new QPushButton("Button")), m_worker(new Worker) 
    { 
        QVBoxLayout * vbox = new QVBoxLayout(this); 
        vbox->addWidget(m_label); 
        vbox->addWidget(m_button); 
        setLayout(vbox); 
         QThread * thread = new QThread(this); 
        m_worker->moveToThread(thread); 
         connect(m_button,SIGNAL(clicked()),this,SLOT(onButtonClicked())); 
        connect(this,SIGNAL(clicked(int)),m_worker,SLOT(slot1(int))); 
        connect(m_worker,SIGNAL(sig1(QString)),m_label,SLOT(setText(QString))); 
        thread->start(); 
    } 
signals: 
    void clicked(int v); 
private slots: 
    void onButtonClicked() 
    { 
        static int v = 0; 
        emit clicked(v); 
        v++; 
    } 
private: 
    QLabel * m_label; 
    QPushButton * m_button; 
    Worker * m_worker; 
};

main 函数还是和尝试一完全一样

控制台输出结果如下

main:  3056961296  
slots1:  3024616304  
slots1:  3024616304  
....

一共两个线程,且二者id不同,说明slot在次线程中

恩。这篇文字似乎又不短了,看来任务二要另起一篇了。

  • — dbzhang800 于 20101023
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

发布者:全栈程序员-用户IM,转载请注明出处:https://javaforall.cn/140419.html原文链接:https://javaforall.cn

【正版授权,激活自己账号】: Jetbrains全家桶Ide使用,1年售后保障,每天仅需1毛

【官方授权 正版激活】: 官方授权 正版激活 支持Jetbrains家族下所有IDE 使用个人JB账号...

(0)


相关推荐

  • js 数组splice_splice会影响原数组吗

    js 数组splice_splice会影响原数组吗今天用到了数组的删除,分别使用了splice和delete方法,记录一下,方便下次查找。原数组是一个关联数组,如vartest=[];test[1]={name:’1′,age:1};test[2]={name:’2′,age:2};test[4]={name:’3′,age:3};console.log(test)长度为5的关联数组,现在开始删除。1.splice方法test.splice(2

  • OpenBmc开发8:devtool简介与使用

    OpenBmc开发8:devtool简介与使用1简介devtool是yocto中的一个工具,此命令行工具作为可扩展SDK(eSDK)的一部分,是基础组件。可以使用devtool来帮助构建,测试和打包eSDK中的软件。可以使用该工具有选择地将构建的内容集成到OpenEmbedded构建系统构建的映像中。一般我们使用该工具主要用于修改源码,在修改后将提交加入到指定的layer中。2命令介绍常用的命令是:Add添加recipesModify提取源码Up…

  • C#使用WebProxy实现代理访问webservice

    C#使用WebProxy实现代理访问webservice1.问题描述标题写的有点含糊不清,不好意思语言表达能力欠佳。。事情是这样的!事情是这样的,在A服务器上有一个webservice,B计算机和C计算机都可以ping通A服务器,但是C计算机的ip备案了而B计算机的ip没有备案,导致我写的一个小程序放在B计算机上调用A服务器的webservice的时候提示我ip非法。那么该如何让这个小程序能在B计算机上正常使用呢?答案是代理服务器!2.w…

  • MyBatis 二级缓存机制[通俗易懂]

    MyBatis 二级缓存机制[通俗易懂]MyBatis 二级缓存机制

  • 中断之NVIC

    中断之NVIC简单的介绍一下NVIC,有点枯燥乏味

  • 九某草 X站cms 渗透篇「建议收藏」

    九某草 X站cms 渗透篇「建议收藏」这一套源码与网上那些X站cms都是一致,那么在奇安信社区上看到了这款,那么也来玩玩,这一套源码的话基本的都是存在后台提权、存储xss、反射XSS、弱口令(至于弱口令这块一般安装后直接使用admin、admin或者某cms名称直接进行登录,那么我们登录进行也是直接忽略过爆破这一段)1、爆破篇…

    2022年10月19日

发表回复

您的电子邮箱地址不会被公开。

关注全栈程序员社区公众号