实际开发中慎用QCoreApplication::processEvents()。
实际开发中遇到问题然后处理问题是提高能力的最直接方式,笔者的文章都是在实际开发过程中发现问题然后去解决问题的过程,希望对读者有帮助。
这两天一直在处理一个程序崩溃的问题,大概的现象是程序跑起来没多久,就直接崩溃掉了,抓取过dump文件用windbg去查具体的问题,但是没有任何效果,然后通过自己调试和测试大致知道问题出在什么地方,就是数据入库的时候导致了程序崩溃。那么就在这个地方下功夫去处理,在插入数据库的函数中发现开发者用到了QCoreApplication::processEvents()接口,然后就去查了一下这个函数的作用。
QCoreApplication::processEvents()的作用,作用就是处理密集型耗时的事情。
当我把入库函数中相关QCoreApplication::processEvents()的语句都屏蔽掉的时候。
int JNDBUtil::InsertTableData(QString tableName, const QVariantMap& params) { BMSLogger::GetInstance().OutputLog(LOG_INFO, "start to set sqlquery"); //QCoreApplication::processEvents(QEventLoop::AllEvents, 200); QMap<QString, QVariant>::const_iterator c_iter; QString property = ""; QString values = ""; for (c_iter = params.begin(); c_iter != params.end(); ++c_iter) { property += "`" + c_iter.key() + "`,"; values += (":" + c_iter.key() + ","); //QCoreApplication::processEvents(QEventLoop::AllEvents, 200); } property = " ( " + property.mid(0, property.length() - 1) + " ) "; values = "Values ( " + values.mid(0, values.length() - 1) + " ) "; QString queryStr = "insert into " + tableName + property + values; //QCoreApplication::processEvents(QEventLoop::AllEvents, 200); return DBHandler::InsertTableData(queryStr, params); }
运行程序,程序没有挂掉,但是程序出现假死的状态,界面根本无法操作,但是后台数据入库还是在进行中,这就说明了一点,数据入库和界面在同一个线程中,处理数据入库消息的时间太长了,界面没办法刷新。
上面也说了,如果我不屏蔽QCoreApplication::processEvents,那么程序在运行20秒左右的时候就会挂掉。
实际上在我们开发的过程中这样做是很不好的,界面刷新和数据入库的操作应该要做到分离,不能在同一个线程中去处理,于是我就将数据入库这部分的操作另外起了一个线程处理,在运行的时候程序就没有出现崩溃的现场。
InBoundThread.h
#pragma once #include <QThread> #include "jnstruct.h" #include <QMap> #include <QVector> #include <QQueue> class JNDBUtil; class DeviceInfoWidget; class ForRecoderInfoSt; struct FormulaRecordInfo { QString RecordStepNo; //从工步配方表中获得的工步号 QString RecordTime; //从工步配方表中获得的记录时间 QString RecordCutOffSet; //从工步配方表中获得的截止设置 }; struct ForRecorderInfo { int chan; ForRecoderInfoSt m_recordinfo; }; class CInBoundThd : public QThread { Q_OBJECT public: CInBoundThd(DeviceInfoWidget *pDeviceInfoWgt); ~CInBoundThd(); static void InitOldMapData(); void GetDBFormula(const QString& formulaname, const QString& celltypename); void GetRecorderInfoAndCNo(const ForRecoderInfoSt &st, int iChan); private: void InitMem(); //更新过程数据和截止数据到数据库 void UpdateDataToDB(const ForRecoderInfoSt &st, int iChan); bool SaveCutOffData(const ForRecoderInfoSt &st, int iChan); bool SaveProcessData(const ForRecoderInfoSt &st, int iChan); protected: void run() override; private: //DeviceInfoWidget *m_pDeviceInfoWgt;//主界面 static QMap<int, ForRecoderInfoSt> g_mapOldStThd; QQueue<ForRecorderInfo> m_TmpFRecordInfo; QVector<FormulaRecordInfo> m_VecFormulaRecordInfo; JNDBUtil *m_pUtil; ForRecoderInfoSt m_TmpRecordInfo; int m_TmpChan; };
InBoundThread.cpp
#include "InBoundThread.h" #include "..\Common/bmslogger.h" #include "..\Common/jndbutil.h" #include "..\Common/globalconststr.h" #include "..\Common/commFuc.h" #include "..\OtherUI/CellBind/cellbindwidget.h" #include <QDateTime> QMap<int, ForRecoderInfoSt> CInBoundThd::g_mapOldStThd; CInBoundThd::CInBoundThd(DeviceInfoWidget *pDeviceInfoWgt) { //m_pDeviceInfoWgt = pDeviceInfoWgt; m_pUtil = new JNDBUtil; InitMem(); } CInBoundThd::~CInBoundThd() { SafeDelete(m_pUtil); } void CInBoundThd::InitOldMapData() { g_mapOldStThd.clear(); for (int i = 0; i < 40; i++) { g_mapOldStThd[i + 1] = ForRecoderInfoSt(); } } void CInBoundThd::GetDBFormula(const QString& formulaname, const QString& celltypename) { m_VecFormulaRecordInfo.clear(); QString sCon = QString("SELECT * FROM work_step_formula WHERE cellType = '%1' AND formulaName = '%2';").arg(celltypename).arg(formulaname); auto list = m_pUtil->GetTableData(sCon); for (auto item : list) { FormulaRecordInfo tmpFormulaInfo; tmpFormulaInfo.RecordStepNo = item[GlobalConstStr::m_gFiled_stepNo].toString(); tmpFormulaInfo.RecordTime = item[GlobalConstStr::m_gFiled_recoderTime].toString(); tmpFormulaInfo.RecordCutOffSet = item[GlobalConstStr::m_gFiled_cutOffSet].toString(); m_VecFormulaRecordInfo.append(tmpFormulaInfo); } } void CInBoundThd::GetRecorderInfoAndCNo(const ForRecoderInfoSt &st, int iChan) { ForRecorderInfo RecordInfo; RecordInfo.chan = iChan; RecordInfo.m_recordinfo = st; //m_TmpFRecordInfo.append(RecordInfo); m_TmpFRecordInfo.enqueue(RecordInfo); } void CInBoundThd::InitMem() { InitOldMapData(); } void CInBoundThd::UpdateDataToDB(const ForRecoderInfoSt &st, int iChan) { try { if (g_mapOldStThd.contains(iChan)) { auto &oldSt = g_mapOldStThd[iChan]; if (m_VecFormulaRecordInfo.size() == 0) { return; } //存入每个配方第一个工步的起始数据 if (st.stepNo == m_VecFormulaRecordInfo[0].RecordStepNo.toUInt() && st.iTime == m_VecFormulaRecordInfo[0].RecordTime.toInt() * 1000) // m_VecFormulaRecordInfo[0].RecordStepNo m_VecFormulaRecordInfo[0].RecordTime.toInt() * 1000 { SaveCutOffData(st, iChan); } if (oldSt.stepNo != 0 && oldSt.stepNo != st.stepNo) { //存入截止数据 SaveCutOffData(oldSt, iChan); if (st.stepNo != 0 && st.stepType != 0) { SaveCutOffData(st, iChan); } } if (st.runMode == JN::EmRunState::emStepStart) { //存入过程数据 BMSLogger::GetInstance().OutputLog(LOG_INFO, "start to store processdata!"); //add by zhurui 2021/5/20 SaveProcessData(st, iChan); } oldSt = st; } } catch (const std::exception&) { BMSLogger::GetInstance().OutputLog(LogLevel::LOG_ERROR, GetCurTime() + QString::fromLocal8Bit("UpdateDataToDB::更新数据到数据库失败!!!")); } } bool CInBoundThd::SaveCutOffData(const ForRecoderInfoSt &st, int iChan) { BMSLogger::GetInstance().OutputLog(LOG_INFO, "start to store cutoffdata"); auto &cellInfo = CellBindWidget::GetCellInfo(); QString cellSN = cellInfo.value(iChan); if (cellSN.isEmpty()) { BMSLogger::GetInstance().OutputLog(LOG_ERROR, GetCurTime() + "->: " + QString::fromLocal8Bit("电芯截止数据记录失败,%1通道没有绑定电芯码!").arg(iChan)); return false; } int runTime = QDateTime::fromString(st.date, "yyyy-MM-dd hh:mm:ss.zzz").toTime_t(); QString id = QString("%1-%2-%3").arg(iChan).arg(cellSN).arg(runTime); QVariantMap map; map["id"] = id; map["batteryCode"] = cellSN; map["channelNo"] = iChan; map["current"] = FormatNum(st.cur); map["voltage"] = FormatNum(st.vol); map["energy"] = FormatNum(st.power); map["currentTime"] = st.date; //add by zhurui 2021/4/8 map["cutoffstepNo"] = st.stepNo; map["cutoffstepType"] = st.stepType; map["cutoffcap"] = FormatNum(st.cap); if (m_pUtil->InsertTableData("formation_cutoffdata", map) == -1) { BMSLogger::GetInstance().OutputLog(LOG_ERROR, GetCurTime() + "->: " + QString::fromLocal8Bit("电芯截止数据记录失败,%1通道").arg(iChan)); return false; } return true; } bool CInBoundThd::SaveProcessData(const ForRecoderInfoSt &st, int iChan) { auto &cellInfo = CellBindWidget::GetCellInfo(); QString cellSN = cellInfo.value(iChan); if (cellSN.isEmpty()) { BMSLogger::GetInstance().OutputLog(LOG_ERROR, GetCurTime() + "->: " + QString::fromLocal8Bit("电芯过程数据记录失败,%1通道没有绑定电芯码!").arg(iChan)); return false; } int runTime = QDateTime::fromString(st.date, "yyyy-MM-dd hh:mm:ss.zzz").toTime_t(); QString id = QString("%1-%2-%3").arg(iChan).arg(cellSN).arg(runTime); QVariantMap map; map["id"] = id; map["batteryCode"] = cellSN; map["channelNo"] = iChan; map["current"] = FormatNum(st.cur); map["voltage"] = FormatNum(st.vol); map["capacity"] = FormatNum(st.cap); map["energy"] = FormatNum(st.power); map["batteryTemperature"] = FormatNum(st.temp); map["ratio"] = FormatNum(st.curRat); map["povl"] = FormatNum(st.outVol); map["stepNo"] = st.stepNo; map["stepType"] = /*m_formationProtocol.GetStepName(*/st.stepType/*)*/; map["sumStep"] = st.accStep; map["loopNo"] = st.loopNo; //map["funcCode"] = st.loopNo;//没有 map["runState"] = st.runMode; map["runTime"] = st.iTime; map["currentTime"] = st.date; if (m_pUtil->InsertTableData("formation_processdata", map) == -1) { BMSLogger::GetInstance().OutputLog(LOG_ERROR, GetCurTime() + "->: " + QString::fromLocal8Bit("电芯过程数据记录失败,%1通道").arg(iChan)); return false; } return true; } void CInBoundThd::run() { while (true) { if (m_TmpFRecordInfo.length() > 200) { ForRecorderInfo m_RunRecorderInfo; m_RunRecorderInfo = m_TmpFRecordInfo.dequeue(); //UpdateDataToDB(m_TmpRecordInfo, m_TmpChan); UpdateDataToDB(m_RunRecorderInfo.m_recordinfo, m_RunRecorderInfo.chan); } } }
在deviceinfowidget.h中声明线程
CInBoundThd* m_pInBoundThd;
在deviceinfowidget.cpp中模块的构造函数中初始化
m_pInBoundThd = new CInBoundThd(this); m_pInBoundThd->start();
析构函数中处理方式
if (m_pInBoundThd != nullptr) { m_pInBoundThd->terminate(); m_pInBoundThd->wait(); delete m_pInBoundThd; }
欢迎各位大佬指正,这其中还有一个问题,就是为什么加上了QCoreApplication::processEvents()以后会挂?需要再去深究一下!
2021年6月1日修改
实际运行中发现一个问题,多线程同步的问题没有考虑到,就是我们常说的加锁处理,在本例中有两个线程对队列进行了操作,一个地方插入队列,一个地方取队列,所以这两个地方就需要加锁,如果按照楼主这样处理程序确实没有问题,但是队列中小于200条的记录
m_devicemutex.lock(); m_TmpFRecordInfo.enqueue(RecordInfo); m_devicemutex.unlock();
void CInBoundThd::run() { while (true) { //msleep(300); m_devicemutex.lock(); if (m_TmpFRecordInfo.size() > 0) { ForRecorderInfo m_RunRecorderInfo; m_RunRecorderInfo = m_TmpFRecordInfo.dequeue(); //UpdateDataToDB(m_TmpRecordInfo, m_TmpChan); m_devicemutex.unlock(); UpdateDataToDB(m_RunRecorderInfo.m_recordinfo, m_RunRecorderInfo.chan); } else { m_devicemutex.unlock(); } } }