Qt多线程调用python并接收调用数据

功能:

        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 &paramters, QVariantMap& result);
    int executeExterProcess(const QString &pyfile, const QStringList &paramters);

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 &paramters, 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 &paramters, 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 &paramters, 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 &paramters)
{
    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

相关推荐

  1. Qt线调用python接收调用数据

    2024-02-22 11:34:01       44 阅读
  2. python调用http接口

    2024-02-22 11:34:01       59 阅读
  3. Qt如何保证控件调用时候的线安全

    2024-02-22 11:34:01       40 阅读

最近更新

  1. docker php8.1+nginx base 镜像 dockerfile 配置

    2024-02-22 11:34:01       94 阅读
  2. Could not load dynamic library ‘cudart64_100.dll‘

    2024-02-22 11:34:01       100 阅读
  3. 在Django里面运行非项目文件

    2024-02-22 11:34:01       82 阅读
  4. Python语言-面向对象

    2024-02-22 11:34:01       91 阅读

热门阅读

  1. 编程笔记 Golang基础 015 数据类型:布尔类型

    2024-02-22 11:34:01       52 阅读
  2. Go 1.22 对 net/http 包的路由增强功能详解

    2024-02-22 11:34:01       45 阅读
  3. go语言内存泄漏检查工具

    2024-02-22 11:34:01       47 阅读
  4. 无人值守称重系统是如何提取车辆数据的

    2024-02-22 11:34:01       47 阅读
  5. 嵌入式Linux下的多线程编程

    2024-02-22 11:34:01       40 阅读
  6. Spring Boot

    2024-02-22 11:34:01       46 阅读
  7. Redis 数据结构详解:底层实现与高效使用场景

    2024-02-22 11:34:01       48 阅读
  8. C语言之删除中间的*

    2024-02-22 11:34:01       56 阅读
  9. 「Python系列」Python输入输出

    2024-02-22 11:34:01       59 阅读
  10. 喝点小酒-胡诌“编程语言学习”

    2024-02-22 11:34:01       46 阅读