从零开始学Qt(77):多线程程序实例 - 投骰子

作为实例,定义一个掷骰子的线程类QDiceThread,然后在主线程中创建和控制子线程的运行。通过这个实例,可以了解多线程程序的基本原理。

QDiceThread的定义

首先新建一个Widget项目。然后依次点击“New File or Project” – “C++” – “C++ Class”新建类,名称设置为QDiceThread,基类设置为QThread,勾选“Add Q_OBJECT”。

类的声明部分如下:

#include <QThread>
class QDiceThread : public QThread
{
Q_OBJECT
public:
  QDiceThread();
  void diceBegin(); //掷一次骰子
  void dicePause(); //暂停
  void stopThread(); //结束线程
protected:
  void run() override; //线程任务
private:
  int m_seq=0; //掷骰子次数序号
  int m_diceValue; //骰子点数
  bool m_Paused=true; //暂停
  bool m_stop=false; //停止
signals:
  void newValue(int seq,int diceValue); //产生新点数的信号
};

重载虚函数run(),在此函数里完成线程的主要任务。

自定义diceBegin()、dicePause()、stopThread()3个公共函数用于线程控制,这3个函数由主线程调用。

定义了一个信号newValue(int seq,int diceValue)用于在掷一次骰子得到新的点数之后发射此 信号,由主线程的槽函数响应以获取值。

QDiceThread类的实现

QDiceThread类的实现代码如下:

#include "qdicethread.h"
#include <QTime>
QDiceThread::QDiceThread()
{ //构造函数
}
void QDiceThread::diceBegin()
{ //开始掷骰子
	m_Paused=false;
}
void QDiceThread::dicePause()
{//暂停掷骰子
	m_Paused=true;
}
void QDiceThread::stopThread()
{//停止线程
	m_stop=true;
}
void QDiceThread::run()
{//线程任务
  m_stop=false; //启动线程时令 m_stop=false
  m_seq=0; //掷骰子次数
  qsrand(QTime::currentTime().msec());//随机数初始化,qsrand 是线程安全的
  while(!m_stop) //循环主体
  {
    if(!m_Paused) {
      m_diceValue=qrand(); //获取随机数
      m_diceValue=(m_diceValue%6)+1;
      m_seq++;
      emit newValue(m_seq,m_diceValue);//发射信号
    }
    msleep(500); //线程休眠 500ms
  }
  quit();//相当于exit(0),退出线程的事件循环
}

其中,run()是线程任务的实现部分,线程开始就执行run()函数。run()函数一般是事件循环过程,根据各种条件或事件处理各种任务。当run()函数退出时,线程的事件循环就结束了。

在run()函数里,初始化变量m_stop和m_seq,用qsrand()函数对随机数种子初始化。run()函数的主循环体是一个while循环,在主线程调用stopThread()函数使m_stop为true,才会退出while循环,调用quit()之后结束线程。

在while循环体内,又根据m_Paused判断当前是否需要掷骰子,如果需要掷骰子,则用随机函数生成一次骰子的点数m_diceValue,然后发射信号newValue(),将m_seq和m_diceValue作为信号参数传递出去。主线程可以设计槽函数与此信号关联,获取这两个值并进行显示。

QDiceThread类的使用

使用QDiceThread类,设计一个应用程序,程序运行界面如图所示。

窗体上方的几个按钮用于控制线程的启动与停止,控制开始与暂停掷骰子。中间的文本框 显示次数和点数,右边根据点数显示资源文件里面的一个图片,图片存储在项目的资源文件里。

下方的一个标签根据QDiceThread的started()和 finished()两个信号显示线程的状态。

窗口类是从QWidget继承的类Widget,其类定义如下(省略了按钮槽函数的定义):

class Widget : public QWidget
{
Q_OBJECT
public:
  Widget(QWidget *parent = nullptr);
  ~Widget();
protected:
	void closeEvent(QCloseEvent *event);
private:
  Ui::Widget *ui;
  QDiceThread threadA;
private slots:
  //自定义槽函数
  void on_threadA_started();
  void on_threadA_finished();
  void on_threadA_newValue(int seq, int diceValue);
};

这里定义了一个QDiceThread类型的变量threadA,重定义了closeEvent()事件,自定义了 3个槽函数。

Widget类的构造函数代码如下:

Widget::Widget(QWidget *parent)
: QWidget(parent), ui(new Ui::Widget)
{//构造函数
  ui->setupUi(this);
  connect(&threadA,SIGNAL(started()),
  	this,SLOT(on_threadA_started()));
  connect(&threadA,SIGNAL(finished()),
  	this,SLOT(on_threadA_finished()));
  connect(&threadA,SIGNAL(newValue(int,int)),
  	this,SLOT(on_threadA_newValue(int,int)));
}

构造函数主要是将threadA的3个信号与Widget自定义的3个槽函数相关联,这3个槽函数的代码如下:

void Widget::on_threadA_started()
{//线程的started()信号的响应槽函数
	ui->labStat->setText ("Thread 状态:thread started");
}
void Widget::on_threadA_finished()
{//线程的finished()信号的响应槽函数
	ui->labStat->setText ("Thread 状态:thread finished");
}
void Widget::on_threadA_newValue(int seq, int diceValue)
{// QDiceThread的newValue()信号的响应槽函数,显示骰子次数和点数
  QString str=QString::asprintf("第 %d 次掷殷子,点数为:%d", seq, diceValue);
  ui->plainTextEdit->appendPlainText(str);
  QPixmap pic; //图片显示
  QString filename=QString::asprintf(":/dice/images/d%d.jpg", diceValue);
  pic.load(filename);
  ui->labPic->setPixmap(pic);
}

started()信号发射时,表示线程开始执行,在标签里显示状态文字。 finished()信号发射时,表示线程结束执行,在标签里显示状态文字。

newValue()是QDiceThread定义的信号,在掷一次骰子获得新的点数后发射,将掷骰子的次数和点数传递过来。槽函数on_threadA_newValue()获取这两个值并显示在文本框里,再根据点数从资源文件里获取相应的图片并显示。

窗口上5个按钮的代码如下:

void Widget::on_btnStartThread_clicked()
{//启动线程按钮
  threadA.start();
  ui->btnStartThread->setEnabled(false);
  ui->btnStopThread->setEnabled(true);
  ui->btnStartDice->setEnabled(true);
  ui->btnStopDice->setEnabled(false);
}
void Widget::on_btnStopThread_clicked()
{//结束线程按钮
  threadA.stopThread(); //结束线程的 run ()函数执行
  threadA.wait();
  ui->btnStartThread->setEnabled(true);
  ui->btnStopThread->setEnabled(false);
  ui->btnStartDice->setEnabled(false);
  ui->btnStopDice->setEnabled(false);
}
void Widget::on_btnStartDice_clicked()
{//开始掷骰子按钮
  threadA.diceBegin();
  ui->btnStartDice->setEnabled(false);
  ui->btnStopDice->setEnabled(true);
}
void Widget::on_btnStopDice_clicked()
{//暂停掷骰子按钮
  threadA.dicePause();
  ui->btnStartDice->setEnabled(true);
  ui->btnStopDice->setEnabled(false);
}
void Widget::on_btnClear_clicked()
{//清空文本按钮
	ui->plainTextEdit->clear();
}

“启动线程”按钮调用线程的start()函数,start()函数会内部调用run()函数开始线程任务的执行。run()函数将内部变量m_Paused初始化为true,所以,启动线程后并不会立即开始掷骰子。

“开始”按钮调用diceBegin()函数,使threadA线程内部变量m_Paused变为false,那么run() 函数里就开始每隔500毫秒产生一次骰子点数,并发射信号newValue()。

“暂停”按钮调用dicePause()函数,使threadA线程内部变量m_Paused变为true,run()函数里不再掷骰子,但是run()函数并没有结束,也就是线程并没有结束。

“结束线程”按钮调用stopThread()函数,使threadA线程内部的m_stop变为true,run()函数 体的while循环结束,执行quit()后线程结束。所以,线程结束就是run()函数执行退出。

重载closeEvent()事件,在窗口关闭时确保线程被停止,代码如下:

void Widget::closeEvent(QCloseEvent *event)
{//窗口关闭事件,必须结束线程
  if(threadA.isRunning()){
    threadA.stopThread();
    threadA.wait();
  }
  event->accept();
}

相关文章

Qt多线程编程两种方式详解(qt开启线程的三种方式)

QT的多线程编程主要有两种方式:第一种是继承自QThread,然后重写run()函数;第二种是继承自QObject,然后把整个对象moveToThread;两种方法比较:第一种方法只有run()函数是...

Qt多线程创建(qt多线程直接处理数据)

【为什么要用多线程?】传统的图形用户界面应用程序都只有一个执行线程,并且一次只执行一个操作。如果用户从用户界面中调用一个比较耗时的操作,当该操作正在执行时,用户界面通常会冻结而不再响应。这个问题可以用...

正点原子I.MX6U嵌入式Qt开发指南:第十章《多线程》

今日头条/西瓜视频/抖音短视频 同名:正点原子原子哥今日头条/西瓜视频/抖音短视频账号:正点原子-原子哥感谢各位的关注和支持,你们的关注和支持是正点原子无限前进的动力。第十章《多线程》我们写的一个应用...

Qt快速入门(工程的创建、UI界面布局、多线程、项目)

本文档将介绍QT工程的创建、UI界面布局,并以计数器为例了解QT中多线程的用法,最终完成一个基础的QT项目。1 创建QT工程文件在安装好QT之后,能够在其安装组件中找到Qt Creator,点击设置项...

Qt多线程1:QThread(Qt多线程通信)

1. Qt多线程概述Qt有两种多线程的方法,其中一种是继承QThread的run函数,另外一种是把一个继承于QObject的类转移到一个Thread里。 Qt4.8之前都是使用继承QThread的ru...

从零开始学Qt(76):什么是多线程?(怎么从0开始学编程)

多线程程序一个应用程序一般只有一个线程,一个线程内的操作是顺序执行的,如果有某个比较消耗时 间的计算或操作,比如网络通信中的文件传输,在一个线程内操作时,用户界面就可能会冻结而不能及时响应。这种情况下...