一个Qt多线程应用的新手笔记

在回顾这个笔记前,应该再去阅读一下dbzhang老师的三篇博客:

Qt线程基础:

http://blog.csdn.net/dbzhang800/article/details/6554104

How to Use QThread in The Right Way Part 1 & 2:

http://blog.debao.me/2013/08/how-to-use-qthread-in-the-right-way-part-1/

http://blog.debao.me/2013/08/how-to-use-qthread-in-the-right-way-part-2/


作为新手,大概了解多线程的初级用法是改写线程子类的run()函数,例如Qt里可以定义一个QThread的子类,然后改写这个子类的run()函数来启动一个新的线程来做一些事情。根据一些了解似乎是只有在run()函数里的部分才会在另一个线程中运行,比如这个QThread子类里有一个叫print()的函数,在主线程调用print(),则print()运行在主线程里。如果在run()里面调用了print(),则print()运行在新的线程里。Qt里面QThread默认的run()函数里面似乎有着一个次线程的事件循环。改写run()函数需要注意用mutex上锁来避免多线程共同访问某个对象来造成错误。

根据博客内容,还有一个方法是用moveToThread()函数来把一个对象“转移”到另一个线程中,新线程中的事件循环可以和主线程用connect()函数进行协作,connect()的ConnectionType参数需要设定成Qt::QueuedConnection,Qt似乎会检测程序是否是多线程,如果是,这个参数自动被设定成QueuedConnection。用了moveToThread()似乎可以很方便的利用Qt信号槽的机制来做一些事情,而不用改写run()函数也不用考虑加锁问题。


我用以上信息写了一个小的双线程的串口程序进行测试,代码如下:

serialporthelper.pro:

QT       += core gui serialport

greaterThan(QT_MAJOR_VERSION, 4): QT += widgets

TARGET = serialPortHelper
TEMPLATE = app

CONFIG+=console

SOURCES += main.cpp\
        mainwindow.cpp \
    threadport.cpp

HEADERS  += mainwindow.h \
    threadport.h

FORMS    += mainwindow.ui

RESOURCES += images/main.qrc

include($$PWD/shared/shared.pri)


threadport.h:

class ThreadPort : public QObject
 {
       Q_OBJECT
public:
    ThreadPort(QObject *parent =0);
    ~ThreadPort();


public slots:
    void writeData(QByteArray data);
    void openPort(QString portname,int baudrate,int databits, int parity, int stopbit);

    void closePort();
    void readInfo();
signals:
    void readData(QByteArray data);
    void isOpen(bool stat);

private:
    QSerialPort *port;
};

threadport.cpp:

#include "threadport.h"
#include <QDebug>
#include <QThread>
ThreadPort::ThreadPort(QObject *parent):QObject(parent),port(new QSerialPort(this))
{
    connect(port,SIGNAL(readyRead()),this,SLOT(readInfo()));
}
void ThreadPort::openPort(QString portname, int baudrate, int databits, int parity, int stopbit)
{
    port->setPortName(portname);
    bool stat= port->open(QIODevice::ReadWrite);
    if(stat)
    {
        port->setBaudRate(QSerialPort::BaudRate(baudrate));
        port->setDataBits(QSerialPort::DataBits(databits));
        port->setParity(QSerialPort::Parity(parity));
        port->setStopBits(QSerialPort::StopBits(stopbit));
        emit isOpen(true);
    }
    else
    {
        emit isOpen(false);
    }
}
void ThreadPort::closePort()
{
    port->flush();
    port->clear();
    port->close();
    emit isOpen(false);
}
void ThreadPort::writeData(QByteArray data)
{
    port->write(data);

}

void ThreadPort::readInfo()
{
  //  qDebug()<<"From created thread: "<<QThread::currentThreadId();

    QByteArray data=port->readAll();
    emit readData(data);
}
ThreadPort::~ThreadPort()
{

}


mainwindow.h:

#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include "led.h"
#include <QMainWindow>
#include <QtSerialPort/QSerialPortInfo>
#include "threadport.h"
#include <QThread>
#include <QTimer>
#include <QFile>

namespace Ui {
class MainWindow;
}

class MainWindow : public QMainWindow
{
    Q_OBJECT

public:
    explicit MainWindow(QWidget *parent = 0);
    void saveSettings();
    void loadSettings();
    void closeEvent(QCloseEvent *evt);
    ~MainWindow();
signals:
    void doWriteData(QByteArray data);
    void doOpenPort(QString portname,int baudrate,int databits, int parity, int stopbit);
    void doClosePort();
public slots:
    void openPort();
    void ledControl(bool isOpen);
    void displayData(QByteArray data);
private slots:
    void writeData();
    void stopDisplayData();
    void autoSend(int state);
    void fileProcess();
    void saveRecievedInfoToFile();
    void aboutQt();
    void disableTimerSpin(int a);
    void fileSend();

private:
    Ui::MainWindow *ui;
    QThread *t;
    ThreadPort *thisPort;
    bool isOpen;
    bool isDisplaying;
    QTimer *autoTimer;
    QFile *file;
    QFile *fileToBeSent;

};

#endif // MAINWINDOW_H

mainwindow.cpp:

#if _MSC_VER >= 1600
#pragma execution_character_set("utf-8")
#endif
#include "mainwindow.h"
#include "ui_mainwindow.h"
#include <QThread>
#include <QDebug>
#include <QFileDialog>
#include <QMessageBox>
#include <QTextStream>
#include <QSettings>

//因为是需要用一个新线程和eventloop结合,所以大部分功能是通过信号槽来实现
//程序一开始的时候,线程何其对应的串口已经被创建,所以按键只能打开或删除串口用
MainWindow::MainWindow(QWidget *parent) :
    QMainWindow(parent),
    ui(new Ui::MainWindow),thisPort(Q_NULLPTR)
  ,isOpen(false),isDisplaying(true),autoTimer(new QTimer(this)),file(Q_NULLPTR),fileToBeSent(Q_NULLPTR)
{
    ui->setupUi(this);
    ui->sendButton->setStyleSheet("color:green");
    ui->led->turnOff();
    ui->mainToolBar->addAction(ui->actionConnect);
    ui->mainToolBar->addAction(ui->actionFile);
    foreach (const QSerialPortInfo &port_info, QSerialPortInfo::availablePorts() )
    {
        ui->portCombo->addItem(port_info.portName());
    }
    // 一般设置
    ui->baudRateCombo->addItem("1200",1200);
    ui->baudRateCombo->addItem("2400",2400);
    ui->baudRateCombo->addItem("4800",4800);
    ui->baudRateCombo->addItem("9600",9600);
    ui->baudRateCombo->addItem("19200",19200);
    ui->baudRateCombo->addItem("38400",38400);
    ui->baudRateCombo->addItem("57600",57600);
    ui->baudRateCombo->addItem("115200",115200);
    ui->dataBitsCombo->addItem("5",5);
    ui->dataBitsCombo->addItem("6",6);
    ui->dataBitsCombo->addItem("7",7);
    ui->dataBitsCombo->addItem("8",8);
    ui->parityCombo->addItem("0",0);
    ui->parityCombo->addItem("2",2);
    ui->parityCombo->addItem("3",3);
    ui->parityCombo->addItem("4",4);
    ui->parityCombo->addItem("5",5);
    ui->stopBitCombo->addItem("1",1);
    ui->stopBitCombo->addItem("1.5",3);
    ui->stopBitCombo->addItem("2",2);
    ui->recieveTextBrowser->setReadOnly(true);
    ui->recieveTextBrowser->document()->setMaximumBlockCount(1000);
    ui->sendTimerSpin->setRange(1,10000);
    connect(ui->openPortButton,SIGNAL(clicked()),this,SLOT(openPort()));
    connect(ui->sendButton,SIGNAL(clicked()),this,SLOT(writeData()));
    connect(ui->quitButton,SIGNAL(clicked()),this,SLOT(close()));
    connect(ui->recieveClearButton,SIGNAL(clicked()),ui->recieveTextBrowser,SLOT(clear()));
    connect(ui->recieveStopButton,SIGNAL(clicked()),this,SLOT(stopDisplayData()));
    connect(ui->sendClearButton,SIGNAL(clicked()),ui->sendTextEdit,SLOT(clear()));
    connect(ui->autoSendCheckBox,SIGNAL(stateChanged(int)),this,SLOT(autoSend(int)));
    connect(autoTimer,SIGNAL(timeout()),this,SLOT(writeData()));
    connect(ui->fileSendButton,SIGNAL(clicked()),this,SLOT(fileProcess()));
    connect(ui->actionConnect,SIGNAL(triggered(bool)),this,SLOT(openPort()));
    connect(ui->actionFile,SIGNAL(triggered(bool)),this,SLOT(saveRecievedInfoToFile()));
    connect(ui->actionAbout,SIGNAL(triggered(bool)),this,SLOT(aboutQt()));
    connect(ui->autoSendCheckBox,SIGNAL(stateChanged(int)),this,SLOT(disableTimerSpin(int)));
    connect(ui->fileSendButton_2,SIGNAL(clicked()),this,SLOT(fileSend()));
    //新线程在这里创建
    t= new QThread(this);//创建一个以主线程为父类的次线程
    thisPort = new ThreadPort(0); //创建一个没有父类的串口类
    connect(thisPort,SIGNAL(isOpen(bool)),this,SLOT(ledControl(bool)));
    connect(this,SIGNAL(doWriteData(QByteArray)),thisPort,SLOT(writeData(QByteArray)));
    connect(thisPort,SIGNAL(readData(QByteArray)),this,SLOT(displayData(QByteArray)));
    connect(this,SIGNAL(doClosePort()),thisPort,SLOT(closePort()));
    connect(this,SIGNAL(doOpenPort(QString,int,int,int,int)),thisPort,SLOT(openPort(QString,int,int,int,int)));
    thisPort->moveToThread(t); //把这个没有父类的串口类移动到新建的线程里
    t->start();  //让这个线程跑 exec()所以可以用这个线程的eventloop 似乎
    loadSettings();
}

MainWindow::~MainWindow()
{
       //不理解为什么父类已经被删除了子类还在跑 如果没有这句花会出现 thread destroyed while thread is still running警告
    delete ui;
}
void MainWindow::openPort()
{
    if(!isOpen)
    {
        emit doOpenPort(ui->portCombo->currentText(),ui->baudRateCombo->currentData().toInt(),
                        ui->dataBitsCombo->currentData().toInt(),ui->parityCombo->currentData().toInt(),
                        ui->stopBitCombo->currentData().toInt());
    }
    else
    {
        emit doClosePort();
    }
}
void MainWindow::ledControl(bool isOpen)
{
    ui->led->turnOn(isOpen);
    this->isOpen=isOpen;
}
void MainWindow::writeData()
{
    if(isOpen)
    {
    //qDebug()<<"From main thread: "<<QThread::currentThreadId();
    QByteArray data;
    if(ui->hexSendCheckBox->isChecked())
    {
        data=QByteArray::fromHex(ui->sendTextEdit->toPlainText().toLatin1());
    }
    else{
        data= ui->sendTextEdit->toPlainText().toLatin1();
    }
    emit doWriteData(data);
    }
    else
        return;
}

void MainWindow::displayData(QByteArray data)
{
    if(isDisplaying){
        if(data.size()!=0)
        {
            if(ui->hexDisplayCheckBox->isChecked())
                ui->recieveTextBrowser->insertPlainText(data.toHex().toUpper());
            else
                ui->recieveTextBrowser->insertPlainText(data);

            if(ui->autoClearCheckBox->isChecked())
                ui->recieveTextBrowser->clear();
        }
    }

}
void MainWindow::stopDisplayData()
{

    isDisplaying=!isDisplaying;
    if(isDisplaying==true)
    {
        ui->recieveStopButton->setText("停止显示");
    }
    else{
        ui->recieveStopButton->setText("已停止显示");
    }
}
void MainWindow::autoSend(int state)
{
    if(state==2)
    {
        autoTimer->setInterval(ui->sendTimerSpin->value());
        autoTimer->start();
    }
    else
    {
        autoTimer->stop();
    }
}
void MainWindow::fileProcess()
{
    QString filename=QFileDialog::getOpenFileName(this,"选择文件",".");
    ui->fileSaveAddress->setText(filename);
}
void MainWindow::saveRecievedInfoToFile()
{
    QString filename = QFileDialog::getExistingDirectory(this,"选择出尊地址",".",QFileDialog::ShowDirsOnly);
    filename=filename+"\\port_save.txt";
    file = new QFile(filename,this);
    if(file->isOpen())
    {
        QMessageBox::critical(this,"文件","文件已被打开");
        return;
    }
    if(!file->open(QIODevice::Append))
    {
        QMessageBox::critical(this,"文件","无法打开文件");
        return;
    }
    QString data=ui->recieveTextBrowser->toPlainText();
    QTextStream in(file);
    for(int i = 0 ; i < data.size(); i ++)
    {
        in<<data[i];
    }
    delete file;

}
void MainWindow::saveSettings()
{
    QSettings settings;
    settings.beginGroup("portGroup");
    settings.setValue("portName", ui->portCombo->currentText());
    settings.setValue("baudRate",ui->baudRateCombo->currentIndex());
    settings.setValue("dataBits",ui->dataBitsCombo->currentIndex());
    settings.setValue("parity",ui->parityCombo->currentIndex());
    settings.setValue("stopBit",ui->stopBitCombo->currentIndex());
    settings.endGroup();
    settings.beginGroup("recieveGroup");
    settings.setValue("autoClearCheckBox",static_cast<int>(ui->autoClearCheckBox->checkState()));
    settings.setValue("hexDisplayCheckBox",static_cast<int>(ui->hexDisplayCheckBox->checkState()));
    settings.endGroup();
    settings.beginGroup("sendGroup");
    settings.setValue("hexSendCheckBox",static_cast<int>(ui->hexSendCheckBox->checkState()));
    settings.setValue("autoSendCheckBox",static_cast<int>(ui->autoSendCheckBox->checkState()));
    settings.setValue("sendTimer",ui->sendTimerSpin->value());
    settings.endGroup();


}
void MainWindow::loadSettings()
{
    QSettings settings;
    settings.beginGroup("portGroup");
    QString name= settings.value("portName").toString();
    int pos = ui->portCombo->findText(name);
    if(pos!=-1)
        ui->portCombo->setCurrentIndex(pos);
    ui->baudRateCombo->setCurrentIndex(settings.value("baudRate").toInt());
    ui->dataBitsCombo->setCurrentIndex(settings.value("dataBits").toInt());
    ui->parityCombo->setCurrentIndex(settings.value("parity").toInt());
    ui->stopBitCombo->setCurrentIndex(settings.value("stopBit").toInt());
    settings.endGroup();
    settings.beginGroup("recieveGroup");
    ui->autoClearCheckBox->setCheckState(Qt::CheckState(settings.value("autoClearCheckBox").toInt()));
    ui->hexDisplayCheckBox->setCheckState(Qt::CheckState(settings.value("hexDisplayCheckBox").toInt()));
    settings.endGroup();
    settings.beginGroup("sendGroup");
    ui->hexSendCheckBox->setCheckState(Qt::CheckState(settings.value("hexSendCheckBox").toInt()));
    ui->autoSendCheckBox->setCheckState(Qt::CheckState(settings.value("autoSendCheckBox").toInt()));
    ui->sendTimerSpin->setValue(settings.value("sendTimer").toInt());
    settings.endGroup();

}
void MainWindow::aboutQt()
{
     QMessageBox::aboutQt(this);
}
void MainWindow::closeEvent(QCloseEvent *evt)
{
    saveSettings();
    emit doClosePort();
    t->terminate();
    t->wait();
    t->quit();
    if(t->isRunning())
    {
        evt->ignore();
        QMessageBox::critical(this,"Warning","线程似乎还在跑");
    }
    else
    {
        delete thisPort;
        evt->accept();
    }

}
void MainWindow:: disableTimerSpin(int a)
{
    if(a==2)
    {
        ui->sendTimerSpin->setDisabled(true);
    }
    else
    {
        ui->sendTimerSpin->setEnabled(true);
    }
}
void MainWindow::fileSend()
{

    fileToBeSent = new QFile(ui->fileSaveAddress->text(),this);

    if(!fileToBeSent)
    {
        QMessageBox::critical(this,"文件","无法打开文件");
        return;
    }
    if(!fileToBeSent->open(QIODevice::ReadOnly))
    {
        QMessageBox::critical(this,"文件","无法打开文件");
        return;
    }
    else
    {

       // QTextStream out(fileToBeSent);
        QByteArray data= fileToBeSent->readAll();
//        while(!out.atEnd())
//        {
//            QString temp= out.readLine();
//            QByteArray data;
            if(isOpen)
            {
//            if(ui->hexSendCheckBox->isChecked())
//            {
//                data=QByteArray::fromHex(data);
//            }
//            else{
//                data= temp.toLatin1();
//            }
            emit doWriteData(data);
            }
            else
            {
                return;
            }


        delete fileToBeSent;
    }

}

main.cpp:

#include "mainwindow.h"
#include "threadport.h"
#include <QApplication>
#include <QObject>


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

    QApplication a(argc, argv);
    a.setApplicationName("redo-voltage-control");
    a.setOrganizationName("HMICN");
    MainWindow w;



    w.show();


    return a.exec();
}


界面是用Qt Designer拉的,没有代码。

我在mainwindow对象里new了一个QThread类,QThread的parent设定成mainwindow的对象,然后我在mainwindow的构造函数里又new了一个threadport的对象,把这个对象moveToThread到QThread对象里。然后就用信号槽来实现简单的功能了。

可以用 去Debug()和

QThread::currentThreadId();

来查看运行情况,看看到底在哪个线程。


评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值