Terminate an ongoing QProcess that is running inside a QThread? [duplicate]

我的未来我决定 提交于 2019-12-02 07:08:39
Kuba Ober

It is really bad style to use a QThread to manage a running process. I'm seeing it again and again and it's some fundamental misunderstanding about how to write asynchronous applications properly. Processes are separate from your own application. QProcess provides a beautiful set of signals to notify you when it has successfully started, failed to start, and finished. Simply hook those signals to slots in an instance of a QObject-derived class of yours, and you'll be all set.

It's bad design if the number of threads in your application can exceed significantly the number of cores/hyperhtreads available on the platform, or if the number of threads is linked to some unrelated runtime factor like number of running subprocesses.

See my other other answer.

You can create QProcess on the heap, as a child of your monitoring QObject. You could connect QProcess's finished() signal to its own deleteLater() slot, so that it will automatically delete itself when it's done. The monitoring QObject should forcibly terminate any remaining running processes when it gets itself destroyed, say as a result of your application shutting down.

Further to the question was how to execute uncontrollably long running functions, say database queries for which there's no asynchronous API, with minimal impact, when interspersed with things for which there is good asynchronous API, such as QProcess.

A canonical way would be: do things synchronously where you must, asynchronously otherwise. You can stop the controlling object, and any running process, by invoking its deleteLater() slot -- either via a signal/slot connection, or using QMetaObject::invokeMethod() if you want to do it directly while safely crossing the thread boundary. This is the major benefit of using as few blocking calls as possible: you have some control over the processing and can stop it some of the time. With purely blocking implementation, there's no way to stop it short of using some flag variables and sprinkling your code with tests for it.

The deleteLater() will get processed any time the event loop can spin in the thread where a QObject lives. This means that it will get a chance between the database query calls -- any time when the process is running, in fact.

Untested code:

class Query : public QObject
{
  Q_OBJECT
public:
  Query(QObject * parent = 0) : QObject(parent) {
    connect(process, SIGNAL(error(QProcess::ProcessError)), SLOT(error()));
    connect(process, SIGNAL(finished(int,QProcess::ExitStatus)), SLOT(finished(int,QProcess::ExitStatus)));
  }
  ~Query() { process.kill(); }
  void start() {
    QTimer::singleShot(0, this, SLOT(slot1()));
  }
protected slots:
  void slot1() {
    // do a database query
    process.start(....);
    next = &Query::slot2;
  }
protected:
  // slot2 and slot3 don't have to be slots
  void slot2() {
    if (result == Error) {...}
    else {...}
    // another database query
    process.start(...); // yet another process gets fired
    next = &Query::slot3;
  }
  void slot3() {
    if (result == Error) {...}
    deleteLater();
  }

protected slots:
  void error() {
    result = Error;
    (this->*next)();
  }
  void finished(int code, QProcess::ExitStatus status) {
    result = Finished;
    exitCode = code;
    exitStatus = status;
    (this->*next)();
  }
private:
  QProcess process; 
  enum { Error, Finished } result;
  int exitCode;
  QProcess::ExitStatus exitStatus;
  void (Query::* next)();
};

Personally, I'd check if the database that you're using has an asynchronous API. If it doesn't, but if the client library has available sources, then I'd do a minimal port to use Qt's networking stack to make it asynchronous. It would lower the overheads because you'd no more have one thread per database connection, and as you'd get closer to saturating the CPU, the overheads wouldn't rise: ordinarily, to saturate the CPU you'd need many, many threads, since they mostly idle. With asynchronous interface, the number of context switches would go down, since a thread would process one packet of data from the database, and could immediately process another packet from a different connection, without having to do a context switch: the execution stays within the event loop of that thread.

QProcess::waitForStarted just signals that your process has started. The mutex in extCmd() method gets unlocked then because you are not waiting for QProcess::waitForFinished in this method. You will exit this method while the child process is still running.

If you want to use a fire&forget type of execution I just you uses QProcess::startDetached

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!