简体   繁体   中英

How to process signals & events properly from long running task in a QThread

I'm trying to create a thread that runs a server in QT. The server runs in a polling loop until its told to stop. I'd like to be able to add slots to my class so I can do stuff such as stop the server from a signal.

QT seems to have a pretty convoluted history with QThread so I've been reading up on blogs about doing threads right and this is what I've come up with.

MyServer is a class derived from QObject:

class MyServer : public QObject {
  Q_OBJECT

public slots:
  void run();
  void stop();

signals:
  void finished();

private:
  void init();
  void pollForConnections(int ms);
  void cleanup();
};

I can run the server from a thread by creating my object, creating a QThread, moving the server to the thread's ownership, connecting up some signals and invoking start():

MyServer *server = new MyServer();
QThread *serverThread = new QThread();
server.moveToThread(serverThread);
// Cause thread to delete itself when server finishes
QObject::connect(serverThread, SIGNAL(started()), server, SLOT(run()));
QObject::connect(server, SIGNAL(finished()), serverThread, SLOT(deleteLater()));
serverThread->start();

So when the QThread starts it emits a started() signal and my run() slot is invoked by the started() signal. The run() method is a loop which spins around until it is told to stop:

void MyServer::run() {
  init();
  while (true) {
    {
      QMutexLocker lock(&mutex_);
      if (stop_) {
        break;
      }
    }
    pollForConnections(100); // 100ms is timeout
  }
  cleanup();
  emit finished();
}

void MyServer::stop() {
  QMutexLocker lock(&mutex_);
  stop_ = true;
}

The issue here of course is that started() will not return to the QThread because it's in my loop. And since my loop has no processing for signals, I couldn't connect stop() to another signal. So presently I just invoke stop() from any thread and use a mutex to protect the flag while I do.

QT has a QEventLoop which I could modify the loop:

QEventLoop eventLoop;
while (true) {
    if (stop_) {
      break;
    }
    pollForConnections(100); // 100ms is timeout
    eventLoop.processEvents();
  }

So potentially I could hook stop() to a signal of some kind and I don't need a mutex to protect stop_ because it will be run on my own thread. I could also process events as I spin around.

This appears to function but I wonder if this is good practice - don't forget that I'm still hanging off that started() signal so are there reentrancy problems here? Also, once I drop out of my loop, is the QThread going to drop into it's default exec() and then run forever? Is it sufficient that by emitting finished() and invoking deleteLater() that the thread will sort itself out properly?

You'd have a better design if you just used QThread's default run() method, and signals-and-slots as your mechanism for everything.

In particular, instead of having a mutex and a polled boolean, you can connect a signal to the thread's quit() method, and when you want the thread to go away, just emit that signal.

Also, there's almost certainly a better way to check for connections than polling for them. The problem with polling (in this case anyway) is that it blocks execution of the thread's other duties for up to 100mS, which will make the thread perform slowly. It also means the thread is using up some CPU cycles every 100mS, even when 99% of the time no connections are actually incoming. Assuming these are TCP connections, check out the QSocketNotifier and/or QTCPServer classes, either of which can handle incoming TCP connections without polling.

Also, once I drop out of my loop, is the QThread going to drop into it's default exec() and then run forever?

Only if you explicitly call up to QThread::run().

Is it sufficient that by emitting finished() and invoking deleteLater() that the thread will sort itself out properly

It might work, but I think calling QThread::quit() or QThread::exit() (and using the default event loop) would be a better practice.

MyServer.h

class MyServer : public QObject {
  Q_OBJECT
public:

  bool isRuning() const;
public slots:
  void run();
  void stop();

signals:
  void finished();

private:
  void init();

  //don't need in this method
  //void pollForConnections(int ms);

  void cleanup();

  mutable QMutex m_mutex;
};

MyServer.cpp

void MyServer::run() {
  init();
  while (isRuning()) {
    //some code here with signals
  }
  cleanup();
  emit finished();
}

void MyServer::stop() {
  QMutexLocker locker(&m_mutex);
  stop_ = true;
}
bool isRuning() const {
  QMutexLocker locker(&m_mutex);
  return stop_;
}
  1. You don't need in QEventLoop. All signals from your infinite loop in while queued to the main thread events queue, if exist connection with object from main thread.
  2. run method calling not from main thread, his call also queued to separate event queue.
  3. After finished signal, serverTread object is queued for deleting via deleteLater method.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM