功能:
qt中用多线程的方式调用python脚本,完成C++和python之间的数据交互。为了满足多任务并发的要求,将调用python的逻辑部分封装到QThread的子类PyApiThread中。
主要实现:
包括PyApiThread的实现以及在主线程中如何使用
PyApiThread的的实现,分.h和.cpp两个文件
#ifndef PYAPITHREAD_H
#define PYAPITHREAD_H
#include "Python.h"
#include <QMap>
#include <QQueue>
#include <QThread>
#include <QVariant>
class QMutex;
class Widget;
class QEventLoop;
class PyApiThread : public QThread
{
Q_OBJECT
public:
explicit PyApiThread(Widget* parent = nullptr);
~PyApiThread();
bool isRunning()const;
void setSleepTime(uint ms);
Q_INVOKABLE void stopRuning();
Q_INVOKABLE void stopPyApi(const QString& pyfile);
void addPyApi(const QString& pyfile, const QString& function,
const QStringList& paramters, const QVariant& attachInfo="");
signals:
void stopThreadWork();
void startPyApiTask(const QString& api);
void finishPyApiTask(const QString& api, bool cancel);
void pyCommandResult(const QVariant& result);
protected:
void run();
private:
QVariant getPyCmd();
QVariant getPyCmdResult();
void addPyApiResult(const QVariant& result);
bool cancelPyApi();
void sleepThread(uint ms);
void initPythonEnviroement();
QVariant parsePyObj(PyObject* obj);
QVariant parsePyList(PyObject* obj);
QVariant parsePyTuple(PyObject* obj);
QVariant parsePyDictionary(PyObject* obj);
QVariant parsePySimpleObj(PyObject* obj);
const char* printPyApiError(const QString& prefix);
QVariant executePyScript(const QString& pyfile, const QString& function,
const QStringList& paramters, const QVariant& attachInfo);
bool needExecuteExterProcess(const QString &pyfile, const QStringList ¶mters, QVariantMap& result);
int executeExterProcess(const QString &pyfile, const QStringList ¶mters);
private:
uint mSleepTime = 80;
bool mRunning = false;
//bool mPlatformInit = true;
QQueue<QVariant> mApiQueue; //后面改成无锁队列
QQueue<QString> mSystemQueue; //后面改成无锁队列
QQueue<QVariant> mApiQueueResult; //后面改成无锁队列
QMutex* mMutex = nullptr;
QMutex* mSysMutex = nullptr;
QEventLoop* mEventLoop = nullptr;
Widget* mParent{nullptr};
//QMap<QString, QVariant(PyApiThread::*)(const QVariantMap&)> mFuncs;
};
#endif // PYAPITHREAD_H
#include "pyapithread.h"
//#include "pyinterface.h"
#include "rlog.h"
#include "widget.h"
#include <QDebug>
#include <QMutex>
#include <QTimer>
#include <QWidget>
#include <QDateTime>
#include <QEventLoop>
#include <QtConcurrent>
#include <QCoreApplication>
class PythonThreadLocker
{
PyGILState_STATE state;
public:
PythonThreadLocker() : state(PyGILState_Ensure())
{}
~PythonThreadLocker() {
PyGILState_Release(state);
}
};
PyApiThread::PyApiThread(Widget *parent)
:mParent(parent)
{
}
PyApiThread::~PyApiThread()
{
if(mMutex)
{
delete mMutex;
mMutex = nullptr;
}
if(mSysMutex)
{
delete mSysMutex;
mSysMutex = nullptr;
}
if(mEventLoop)
{
mEventLoop->quit();
delete mEventLoop;
mEventLoop = nullptr;
}
}
void PyApiThread::stopRuning()
{
mRunning = false;
mEventLoop->quit();
emit stopThreadWork();
qDebug()<<"stopRuning";
}
void PyApiThread::setSleepTime(uint ms)
{
if(ms < 50 || ms > 1000*60)
return;
mSleepTime = ms;
}
bool PyApiThread::isRunning() const
{
return mRunning;
}
void PyApiThread::addPyApi(const QString &pyfile, const QString &function, const QStringList ¶mters, const QVariant& attachInfo)
{
//加上时间戳,过滤掉时间范围内的相同指令
qint64 cur = QDateTime::currentMSecsSinceEpoch();
//qint64 last = getPyCmd().toMap()["addtime"].toULongLong();
//if((cur-last) < 300) return; //这个时间太短会导致命令执行问题
QVariantMap map;
map["api"] = pyfile;
map["func"] = function;
map["param"] = paramters;
map["addtime"] = cur;
map["attach"] = attachInfo;
QMutexLocker locker(mMutex);
mApiQueue.enqueue(map);
}
QVariant PyApiThread::getPyCmd()
{
QMutexLocker locker(mMutex);
if(mApiQueue.isEmpty())
return QVariant();
return mApiQueue.dequeue();
}
void PyApiThread::run()
{
mRunning = true;
mMutex = new QMutex;
mSysMutex = new QMutex;
mEventLoop = new QEventLoop;
connect(mParent, &Widget::stopPyApiThread,
this, &PyApiThread::stopRuning, Qt::QueuedConnection);
//初始化python环境
initPythonEnviroement();
Py_BEGIN_ALLOW_THREADS
while(mRunning)
{
bool cancel = false;
if(cancelPyApi())
{
cancel = true;
//mApiQueueResult.clear();
//continue;
}
//如果某条指令执行失败,则同设备下的所有该指令都取消执行
//process ....
//取消多入多出的规则,改成多入一出
QVariant result = getPyCmdResult();
if(result.isValid() && !result.isNull())
{
emit pyCommandResult(result);
emit finishPyApiTask(result.toMap()["cmd"].toString(),cancel);
}
QVariantMap cmd = getPyCmd().toMap();
if(cmd.isEmpty())
{
sleepThread(mSleepTime);
continue;
}
const QString& file = cmd["api"].toString();
const QString& func = cmd["func"].toString();
const QStringList& param = cmd["param"].toStringList();
const QVariant& attach = cmd["attach"];
qDebug()<<"PyApiThread input="<<cmd;
//取消多入多出的规则,改成多入一出
//auto future = QtConcurrent::run([file, func, param, attach, this]{
emit startPyApiTask(file);
PythonThreadLocker pyLocker;
addPyApiResult(executePyScript(file, func, param, attach));
//});
//don't know if there is useful.yes,no useful
//QCoreApplication::processEvents();
qDebug()<<"finished a pythron api work";
sleepThread(mSleepTime);
}
Py_END_ALLOW_THREADS
Py_Finalize();
quit();
}
void PyApiThread::addPyApiResult(const QVariant &result)
{
//单线程顺序执行,所以就不加锁了
//QMutexLocker locker(mMutex);
mApiQueueResult.enqueue(result);
}
QVariant PyApiThread::getPyCmdResult()
{
//单线程顺序执行,所以就不加锁了
//QMutexLocker locker(mMutex);
if(mApiQueueResult.isEmpty())
return QVariant();
return mApiQueueResult.dequeue();
}
void PyApiThread::stopPyApi(const QString &pyfile)
{
QMutexLocker locker(mSysMutex);
mSystemQueue.enqueue(pyfile);
}
bool PyApiThread::cancelPyApi()
{
bool ret = false;
QMutexLocker locker(mSysMutex);
while(!mSystemQueue.isEmpty())
{
ret = true;
const auto& sys = mSystemQueue.dequeue();
foreach(const auto& cmd, mApiQueue)
{
if(cmd.toMap()["api"].toString() == sys)
mApiQueue.removeAll(cmd);
}
}
return ret;
}
void PyApiThread::sleepThread(uint ms)
{
if(!mRunning) return;
QTimer::singleShot(ms, mEventLoop, &QEventLoop::quit);
mEventLoop->exec();
}
void PyApiThread::initPythonEnviroement()
{
Py_Initialize();
if(!Py_IsInitialized())
return;
//char path[]="/usr/lib";
//QString home = Py_GetPythonHome();
QString appDir = qApp->applicationDirPath();
appDir = "sys.path.append('"+appDir+"/scripts/')";
const char* homePath = appDir.toStdString().c_str();
Py_SetPythonHome(const_cast<char*>(homePath));
PyRun_SimpleString("import sys");
PyRun_SimpleString("import numpy"); //only load once during the process life
//PyRun_SimpleString("sys.path.append('./scripts/')");
PyRun_SimpleString(appDir.toStdString().c_str());
PyEval_InitThreads();
}
QVariant PyApiThread::parsePyList(PyObject *obj)
{
if(!obj) return QVariant();
int listSize = PyList_Size(obj);
if(!listSize) return QVariant();
QVariantList result;
for(int i = 0; i < listSize; ++i)
{
PyObject* Item = PyList_GetItem(obj, i);
QVariant value = parsePyObj(Item);
if(value.isValid())
result.append(value);
//Py_DECREF(Item);//can not do this, otherwise will crash
}
if(result.isEmpty())
return QVariant();
if(1 == result.size())
return result.first();
return result;
}
QVariant PyApiThread::parsePyTuple(PyObject *obj)
{
if(!obj) return QVariant();
int tupleSize = PyTuple_Size(obj);
if(!tupleSize) return QVariant();
QVariantList result;
for(int i = 0; i < tupleSize; ++i)
{
PyObject* Item = PyTuple_GetItem(obj, i);
QVariant value = parsePyObj(Item);
//Py_DECREF(Item);//can not do this, otherwise will crash
if(value.isValid() && !value.isNull())
{
// bool tyList = value.type() == QVariant::List;
// if(tyList && value.toList().size() == 1)
// {
// result.append(value.toList().first());
// continue;
// }
result.append(value);
}
}
if(result.isEmpty())
return QVariant();
if(1 == result.size())
return result.first();
return result;
}
QVariant PyApiThread::parsePyDictionary(PyObject *obj)
{
if(!obj) return QVariant();
int size = PyDict_Size(obj);
if(!size) return QVariant();
QVariantHash result;
auto* keys = PyDict_Keys(obj);
for(int i = 0; i < size; ++i)
{
auto key = PyList_GetItem(keys,i);
auto val = PyDict_GetItem(obj, key);
QString strKey;
if(PyString_Check(key))
strKey = PyString_AsString(key);
else if(PyInt_Check(key))
strKey = QString::number(PyLong_AsLong(key));
else if(PyFloat_Check(key))
strKey = QString::number(PyFloat_AsDouble(key));
//char *strKey = PyString_AsString(key);
auto keyValue = parsePyObj(val);
result[strKey] = keyValue;
}
// if(result.isEmpty())
// return QVariant();
// if(1 == result.size())
// return result.first();
return result;
}
QVariant PyApiThread::parsePyObj(PyObject *obj)
{
if(!obj) return QVariant();
if(PyList_Check(obj))
return parsePyList(obj);
else if(PyTuple_Check(obj))
return parsePyTuple(obj);
else if(PyDict_Check(obj))
return parsePyDictionary(obj);
return parsePySimpleObj(obj);
}
QVariant PyApiThread::parsePySimpleObj(PyObject *obj)
{
if(!obj) return QVariant();
QString str;
if(PyString_Check(obj))
{
str = PyString_AsString(obj);
QVariant value(str.trimmed());
return value;
}
int iNum = 0;
if(PyInt_Check(obj))
{
iNum = PyLong_AsLong(obj);
return iNum;
}
double fNum = 0;
if(PyFloat_Check(obj))
{
fNum = PyFloat_AsDouble(obj);
return fNum;
}
return QVariant();
}
const char *PyApiThread::printPyApiError(const QString &prefix)
{
PyObject* ptype,*pvalue,*ptraceback;
PyErr_Fetch(&ptype,&pvalue,&ptraceback);
PyObject* pystr = PyObject_Str(pvalue);
char *str = PyString_AsString(pystr);
if(pystr) Py_DECREF(pystr);
if(ptype) Py_DECREF(ptype);
if(pvalue) Py_DECREF(pvalue);
if(ptraceback) Py_DECREF(ptraceback);
qInfo()<<"[error]="<<prefix;
return str;
}
QVariant PyApiThread::executePyScript(const QString &pyfile, const QString &function,
const QStringList ¶mters, const QVariant& attachInfo)
{
qDebug()<<"executePyScript begin";
qInfo()<<"[api]="<<pyfile<<"[func]="<<function<<"[param]="<<paramters;
if(pyfile.isEmpty() || function.isEmpty())
return QVariant();
QVariantMap map;
map["cmd"] = pyfile;
map["attach"] = attachInfo;
map["paramter"] = paramters;
if(needExecuteExterProcess(pyfile, paramters, map))
{
qDebug()<<"executePyScript finish";
return map;
}
// if(!mPlatformInit)
// {
// mPlatformInit = false;
// QVariantMap errorMap;
// errorMap["error"] = "Please complete platform initialization first";
// return errorMap;
// }
// setbuf(stdout, nullptr);
// setbuf(stderr, nullptr);
// freopen("out.txt", "a", stdout);
// freopen("error.txt", "a", stderr);
// std::cout<<pyfile.toStdString().c_str()<<function.toStdString().c_str()<<std::endl;
//PyRun_SimpleString 执行简单的Python语句(就好像我们在Python的交互式环境中输入的一条语句那样)
QVariantMap errorMap;
errorMap["cmd"] = pyfile;
errorMap["attach"] = attachInfo;
auto module = PyImport_ImportModule(pyfile.toStdString().c_str());
if(!module)
{
auto ret = printPyApiError("import file error");
qDebug()<<"ImportModule error="<<ret;
errorMap["error"] = ret;
return QVariant(errorMap);
}
auto func= PyObject_GetAttrString(module, function.toStdString().c_str());
if(!func)
{
auto ret = printPyApiError("load func error");
qDebug()<<"GetAttrString error="<<ret;
errorMap["error"] = ret;
return QVariant(errorMap);
}
int size = paramters.size();
PyObject* args = PyTuple_New(size);
for(int i = 0; i < paramters.size(); ++i)
{
PyObject* arg = Py_BuildValue("s", paramters[i].toStdString().c_str());
PyTuple_SetItem(args, i, arg);
}
if(0 == size)
{
//delete args;
args = nullptr;
}
//Python中函数的参数以元组的方式传入的
auto value = PyObject_CallObject(func, args);
if(!value)
{
//如果是因为没有初始化导致调用失败,则发出错误信号并对应其错误码
if(args) Py_DECREF(args);
auto ret = printPyApiError("call func error");
qDebug()<<"CallObject error="<<ret;
errorMap["error"] = ret;
PyErr_Clear(); //如果出现同一串口多操作的错误,考虑将线程停掉再重新start
return QVariant(errorMap);
}
// if(args)
// Py_DECREF(args);
map["result"] = parsePyObj(value);
qInfo()<<"[result]="<<map["result"];
//Py_DECREF 来解除Python对象的引用,以便Python的垃圾回收器能正常的回收这些对象的内存
//注释掉这几段释放内存的调用后,再调用解析返回值函数就不会崩溃了
Py_DECREF(func);
Py_DECREF(value);
Py_DECREF(module);
if(args) Py_DECREF(args);
qDebug()<<"executePyScript end";
return map;
}
bool PyApiThread::needExecuteExterProcess(const QString &pyfile, const QStringList ¶mters, QVariantMap& result)
{
//一些比较特殊的脚本
if("api_platforminit" == pyfile)
{
result["result"] ="init pass";
int code = executeExterProcess(pyfile, paramters);
qDebug()<<"executeExterProcess exit code ="<<code;
QString errorCode = QString::number(code);
if(255 == code)
result["result"] = "fail(lost assiant process)";
else if(1 != code)
result["result"] = "fail.error code="+errorCode;
return true;
}
return false;
}
int PyApiThread::executeExterProcess(const QString &pyfile, const QStringList ¶mters)
{
qDebug()<<"executeExterProcess begin";
QProcess bash;
connect(this, &PyApiThread::stopThreadWork, this, [&]{
bash.terminate();
bash.kill();
});
QString app = qApp->applicationDirPath();
app = app + "/ExternProcess";
QStringList list;
list<<pyfile<<paramters;
bash.start(app, list);
bash.waitForStarted();
qApp->processEvents();
bash.waitForFinished();
int code = bash.exitCode();
bash.kill();
bash.close();
return code;
}
PyApiThread类的使用,在mainThread中:
在构造函数中完成线程类的实例化
{
mPyApiThread = new PyApiThread(this);
connect(mPyApiThread, &PyApiThread::startPyApiTask, this, &Widget::startPyApiTask);
connect(mPyApiThread, &PyApiThread::finishPyApiTask, this, &Widget::finishPyApiTask);
connect(mPyApiThread, &PyApiThread::pyCommandResult, this, [&](const QVariant& result) {
const QVariantMap& map = result.toMap();
processApiResult(map["cmd"].toString(), map);
});
mPyApiThread->start();
}
在其它槽函数中使用PyApiThread方法
{
mPyApiThread->addPyApi("api_readuid",
"api_readuid",
apiList.first().toStringList());
}
void Widget::closeEvent(QCloseEvent *event)
{
if(mPyApiThread->isRunning())
{
//exitApiThread();
emit stopPyApiThread();
}
QWidget::closeEvent(event);
qDebug()<<"closeEvent end";
}
由于使用python相关库,所以在.pro中也要引入相关的库
#python2.7
unix:!macx: LIBS += -L$$PWD/../../../../../usr/lib/python2.7/config-x86_64-linux-gnu/ -lpython2.7
INCLUDEPATH += $$PWD/../../../../../usr/lib/python2.7/config-x86_64-linux-gnu \
/usr/include/python2.7
DEPENDPATH += $$PWD/../../../../../usr/lib/python2.7/config-x86_64-linux-gnu