简体   繁体   English

如何从 QThread 中的长时间运行的任务中正确处理信号和事件

[英]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.我正在尝试创建一个在 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. QT 似乎与 QThread 有着相当复杂的历史,所以我一直在阅读有关正确处理线程的博客,这就是我想出的。

MyServer is a class derived from QObject: MyServer 是从 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():我可以通过创建对象、创建 QThread、将服务器移至线程的所有权、连接一些信号并调用 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.因此,当 QThread 启动时,它会发出一个 started() 信号,而我的 run() 槽则由 started() 信号调用。 The run() method is a loop which spins around until it is told to stop: run() 方法是一个循环,它会一直旋转直到被告知停止:

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.这里的问题当然是开始()不会返回到 QThread 因为它在我的循环中。 And since my loop has no processing for signals, I couldn't connect stop() to another signal.由于我的循环没有处理信号,我无法将 stop() 连接到另一个信号。 So presently I just invoke stop() from any thread and use a mutex to protect the flag while I do.所以目前我只是从任何线程调用 stop() 并在我这样做时使用互斥锁来保护标志。

QT has a QEventLoop which I could modify the loop: QT 有一个 QEventLoop,我可以修改循环:

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.因此,可能我可以将 stop() 挂钩到某种信号,并且我不需要互斥锁来保护 stop_ 因为它将在我自己的线程上运行。 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?另外,一旦我退出循环,QThread 是否会进入它的默认 exec() 然后永远运行? Is it sufficient that by emitting finished() and invoking deleteLater() that the thread will sort itself out properly?通过发出finished() 并调用deleteLater() 线程将自己正确排序是否足够?

You'd have a better design if you just used QThread's default run() method, and signals-and-slots as your mechanism for everything.如果您只是使用 QThread 的默认 run() 方法和信号和插槽作为您的一切机制,那么您会有更好的设计。

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.特别是,您可以将信号连接到线程的 quit() 方法,而不是使用互斥锁和轮询布尔值,并且当您希望线程离开时,只需发出该信号即可。

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.轮询的问题(无论如何在这种情况下)是它阻止线程执行其他任务长达 100 毫秒,这将使线程执行缓慢。 It also means the thread is using up some CPU cycles every 100mS, even when 99% of the time no connections are actually incoming.这也意味着线程每 100 毫秒就会使用一些 CPU 周期,即使 99% 的时间实际上没有连接传入也是如此。 Assuming these are TCP connections, check out the QSocketNotifier and/or QTCPServer classes, either of which can handle incoming TCP connections without polling.假设这些是 TCP 连接,请查看QSocketNotifier和/或QTCPServer类,它们中的任何一个都可以处理传入的 TCP 连接而无需轮询。

Also, once I drop out of my loop, is the QThread going to drop into it's default exec() and then run forever?另外,一旦我退出循环,QThread 是否会进入它的默认 exec() 然后永远运行?

Only if you explicitly call up to QThread::run().仅当您显式调用 QThread::run() 时。

Is it sufficient that by emitting finished() and invoking deleteLater() that the thread will sort itself out properly通过发出finished() 和调用deleteLater() 线程将自己正确排序是否足够

It might work, but I think calling QThread::quit() or QThread::exit() (and using the default event loop) would be a better practice.它可能有效,但我认为调用 QThread::quit() 或 QThread::exit() (并使用默认事件循环)会是更好的做法。

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我的服务器.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.在 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. run 方法不是从主线程调用的,他的调用也排队到单独的事件队列中。
  3. After finished signal, serverTread object is queued for deleting via deleteLater method.完成信号后, serverTread 对象通过 deleteLater 方法排队等待删除。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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