qt QProcess

qt QProcess


QProcess常用三个函数:

QProcess::execute():

以阻塞方式打开外部程序,只有当外部程序执行完后才继续往后执行现程序。其中,外部程序的标准输出、标准错误都是重定向到现程序的标准输出和标准错误。

QProcess::start():               

以子进程的方式打开外部程序,外部进程和现进程执行互不干扰,但外部进程的父进程是现进程。

QProcess::startDetached():


以分离方式打开外部程序,外部进程和现进程执行互不干扰,外部进程的父进程是系统的init进程。

其中start()函数为普通成员函数,execute()和startDetached()为静态成员函数

函数原型:

    void start(const QString &program, const QStringList &arguments, OpenMode mode = ReadWrite);

    int execute(const QString &program, const QStringList &arguments);

    bool startDetached(const QString &program, const QStringList &arguments, 
                        const QString &workingDirectory = QString(), qint64 *pid = Q_NULLPTR);

以上三个函数都是另起一个进程来运行已经存在的外部可执行程序,QProcess类支持与新建的进程进行通信(通过信号与槽的方式)。QProcess允许将一个进程视为一个顺序I/O设备。可以像是用QTcpSocket访问网络连接一样来读写一个进程,例如可以调用read()、readLine()和getChar()等成员函数从新起进程的标准输出读取数据,可以使用write()向进程的标准输入进行写入。

下面使用自己实现的用于守护Python脚本运行的Daemon类作为举例,说明QProcess类的使用。注意,Daemon类的除 构造函数的所有函数都不是线程安全的,切勿直接在另一线程直接调用(可以尝试使用QMetaObject::invoke()函数);

daemon.h文件:

#ifndef DAEMON_H
#define DAEMON_H

#include <QObject>
#include <QMap>
#include <time.h>

class QStringList;
class QProcess;

class Daemon : public QObject
{
    Q_OBJECT
public:
    explicit Daemon(const QStringList &, QObject *parent = 0);
    ~Daemon();
    void setProStatus(bool status) { isRunning = status;}
    bool proStatus() { return isRunning;}


signals:
    void restartFailed(QString);
    void noPythonEnv();
    void closeFailed(QString);
    void readyRead(QString, QString);
    void startRun(QString );
    void terminateRun(QString );

public slots:
    void startAllPro();
    void closeAllPro();
    void setProPaths(const QStringList );
private slots:
    void output();

private:
    QMap<QProcess *, QString> proMap;
    QList<QProcess *> proList;
    time_t * preTime;
    int * crashTimes;
    void proExitHandler(/*int exitCode, QProcess::ExitStatus exitStatus*/);
    bool isRunning = false;
};

#endif // DAEMON_H

daemon.cpp文件:

#include "daemon.h"
#include <QStringList>
#include <QProcess>
#include <QFileInfo>
#include <QDebug>
#include <iostream>
#include <QThread>

Daemon::Daemon(const QStringList & proPaths, QObject *parent) : QObject(parent)
{
    for(auto iterator = proPaths.begin(); iterator != proPaths.end(); iterator++)
    {
        QProcess * temp = new QProcess(this);
        proList.push_back(temp);
        proMap.insert(temp, *iterator);
        connect(temp, &QProcess::readyRead, this, &Daemon::output);
        /*设置启动py文件的运行路径,对于依赖路径的py文件很重要*/
        temp->setWorkingDirectory(QFileInfo(*iterator).path());
    }
    preTime = new time_t[proList.count()];
    crashTimes = new int[proList.count()];
    for(int i = 0; i < proList.count(); i++)
    {
        preTime[i] = time(NULL);
        crashTimes[i] = 0;
    }
}
Daemon::~Daemon()
{
    closeAllPro();
    delete[] preTime;
    delete[] crashTimes;
}

void Daemon::proExitHandler(/*int exitCode, QProcess::ExitStatus exitStatus*/)
{
    QProcess * senderPro = static_cast<QProcess *>(sender());

    /*发出进程终止信号*/
    emit terminateRun(proMap[senderPro]);

    /*若对同一程序重启10次且相邻两次重启时间小于1秒则判断为该进程无法重启,抛出重启失败信号*/
    int index = proList.lastIndexOf(senderPro);
    if(time(NULL) - preTime[index] <= 1)
    {
        crashTimes[index]++;
    }
    else
    {
        crashTimes[index] = 0;
    }
    preTime[index] = time(NULL);
    if(crashTimes[index] > 10/*0*/)
    {
        qCritical() << proMap[senderPro] + " restart failed!";
        disconnect(senderPro, static_cast<void (QProcess::*)(int, QProcess::ExitStatus)>(&QProcess::finished), this, &Daemon::proExitHandler);
        emit restartFailed(proMap[senderPro]);
    }
    else
    {
        senderPro->start("python", QStringList() << "-u" << proMap[senderPro]);
        bool rstSuccess = senderPro->waitForStarted(30000);
        if(!rstSuccess)
        {
            /*emit noPythonEnv();*/
        }
        else
        {
            /*重启进程成功发出进程开始运行信号*/
            emit startRun(proMap[senderPro]);
        }
    }
}

void Daemon::startAllPro()
{
    if(proStatus() == true)
        return;
    for(auto iterator = proList.begin(); iterator != proList.end(); iterator++)
    {
        QProcess * temp = *iterator;
        connect(temp, static_cast<void(QProcess::*)(int, QProcess::ExitStatus)>(&QProcess::finished), this, &Daemon::proExitHandler);
        temp->start("python", QStringList() << "-u" << proMap[temp]);
        bool rstSuccess = temp->waitForStarted(30000);
        if(!rstSuccess)
        {
            emit noPythonEnv();
            disconnect(temp, static_cast<void(QProcess::*)(int, QProcess::ExitStatus)>(&QProcess::finished), this, &Daemon::proExitHandler);
        }
        else
        {
            emit startRun(proMap[temp]);
        }
    }
    setProStatus(true);
}

void Daemon::closeAllPro()
{
    if(proStatus() == false)
        return;
    for(auto iterator = proList.rbegin(); iterator != proList.rend(); iterator++)
    {
        QProcess * temp = *iterator;
        disconnect(temp, static_cast<void (QProcess::*)(int, QProcess::ExitStatus)>(&QProcess::finished), this, &Daemon::proExitHandler);
        temp->close();
        bool closeSuccess = temp->waitForFinished(30000);
        //这里永远返回false,原因待查,所以terminateRun信号在if中也发送,因为else永远不成立
        if(!closeSuccess)
        {
            emit closeFailed(proMap[temp]);
            emit terminateRun(proMap[temp]);
        }
        else
        {
            emit terminateRun(proMap[temp]);
        }
    }
    setProStatus(false);
}

void Daemon::setProPaths(const QStringList proPaths)
{
    for(auto iterator = proList.rbegin(); iterator != proList.rend(); iterator++)
    {
        QProcess * temp = *iterator;
        disconnect(temp, static_cast<void (QProcess::*)(int, QProcess::ExitStatus)>(&QProcess::finished), this, &Daemon::proExitHandler);
        temp->close();
        bool closeSuccess = temp->waitForFinished(30000);
        if(!closeSuccess)
        {
            emit closeFailed(proMap[temp]);
        }
        delete temp;
    }
    proMap.clear();
    proList.clear();
    delete[] preTime;
    delete[] crashTimes;
    for(auto iterator = proPaths.begin(); iterator != proPaths.end(); iterator++)
    {
        QProcess * temp = new QProcess(this);
        proList.push_back(temp);
        proMap.insert(temp, *iterator);
        connect(temp, &QProcess::readyRead, this, &Daemon::output);
        temp->setWorkingDirectory(QFileInfo(*iterator).path());
    }
    preTime = new time_t[proList.count()];
    crashTimes = new int[proList.count()];
    for(int i = 0; i < proList.count(); i++)
    {
        preTime[i] = time(NULL);
        crashTimes[i] = 0;
    }
}

void Daemon::output()
{
    QProcess * proSender = static_cast<QProcess *>(sender());
    emit readyRead(proMap[proSender], QString(proSender->readAll()));
}

(注:Daemon类对QProcess::start()函数的调用使用 "-u"参数,是为了读取python脚本标准输出流的内容,python解释器在检测到不是从终端启动解释器就会关闭向标准输出流的输出,"-u"参数可以有效解决这个问题,但并不能完全解决,如果脚本调用的库中例如caffe,tensorflow等向标准输出中写入信息,即使加"-u"参数也不能获取输出,由于对python不是很熟悉,所以这个问题也没得到解决)

Daemon类的实现中我们只用到了QProcess类的两个信号,QProcess::finished和QProcess::readyRead,用于finished用于重启crash掉的脚本,readyRead用于读取脚本运行时输出的信息(无法输出Python脚本调用库的输出信息)。

Daemon类构造函数默认传入一个QStringList参数,代表了所有要启动的python脚本的路径,注意路径可以重复,同时QStringList对象的成员顺序也代表了程序的启动顺序,使用Daemon时可以在构造函数中传入一个空的QStringList对象,之后再调用startAllPro()函数之前调用setProPaths()函数重新设置路径。Daemon类中的QMap<QProcess *, QString> proMap成员存储了QProcess类对象指针和此对象守护的python脚本的路径,这样方便程序维护。

Daemon类对象一旦调用startAllPro()函数,就会启动传入路径的所有python脚本,当脚本退出,不论是正常还是异常退出,Daemon类对象都会重新开启此脚本,可以参考Daemon类的信号,除了closeFailed()信号没有实现,有点问题,其它正常。

restartFailed(QString),在Daemon尝试连续重启python脚本10次,相邻两次间隔小于1秒,重启全部失败的情况下发出,noPythonEnv()在首次启动python解释器失败时发出,因为即使python脚本文件有问题,python解释器依然可以启动成功,所以waitForStarted()函数返回false只可能时无python环境;Daemon::readyRead()在收到QProcess子对象发出的QProcess::readyRead()信号之后调用的output私有槽函数中发出,用于将读取信息发送到目标对象,目标对象可以存在于另一个线程;其它信号不一一赘述。

下面说明Daemon类的使用,Daemon类对象可以在主线程使用,也可以在另外的子线程使用,在子线程使用时可以参见:

https://blog.csdn.net/xiaoyink/article/details/79582429中讲述的DaemonThread类,这个类实现了在子线程中使用了Daemon类,注意Daemon类的成员函数都是非线程安全的,所以在主线程调用时使用QMetaObject::invokeMethod()函数;

MainWindow.cpp文件:

void MainWindow::startDaemon()
{
    for(auto iterator = mapComb.begin(); iterator != mapComb.end(); iterator++)
    {
        QPushButton * temp = iterator.key();
        temp->setEnabled(false);
    }
    ui->openServerPB->setEnabled(false);
    ui->clearPathsPB->setEnabled(false);
    QStringList strListPaths;
    /*之所以没有直接遍历mapPaths是因为,以下这种方式可以保证按顺序启动进程*/
    for(auto iterator = listPaths.begin(); iterator != listPaths.end(); iterator++)
    {
        QString temp = (mapComb[*iterator])->path->text();
        if(!temp.isEmpty())
        {
            strListPaths << temp;
        }
    }
for(auto iterator = strListPaths.begin(); iterator != strListPaths.end(); iterator++)
{
    QString temp = *iterator;
    std::cout <<temp.toStdString() << std::endl;
}
    /*采用这种方式调用是因为Daemon类成员函数非线程安全函数*/
    QMetaObject::invokeMethod(daemonThread->daemon, "setProPaths", Qt::QueuedConnection, Q_ARG(QStringList, strListPaths));
    //daemonThread->daemon->setProPaths(strListPaths);
    QMetaObject::invokeMethod(daemonThread->daemon, "startAllPro", Qt::QueuedConnection);
}

void MainWindow::endDaemon()
{
    QMetaObject::invokeMethod(daemonThread->daemon, "closeAllPro", Qt::QueuedConnection);
    for(auto iterator = mapComb.begin(); iterator != mapComb.end(); iterator++)
    {
        QPushButton * temp = iterator.key();
        temp->setEnabled(true);
    }
    ui->openServerPB->setEnabled(true);
    ui->clearPathsPB->setEnabled(true);
}

这是主界面上开始运行python脚本服务和结束运行两个按键对应的槽函数,其中daemonThread->daemon对象即使Daemon类对象。

The source code see also: https://github.com/Kylin-Wei/ccr

  • 3
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值