在回顾这个笔记前,应该再去阅读一下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();
来查看运行情况,看看到底在哪个线程。