线程之间的Qt连接类型:为什么这样做



当我试图让一个多摄像头系统使用不同的线程来处理不同的摄像头时,我无法在不同的线程之间获得正确的信号和插槽。我知道发送信号的对象和相关插槽的对象生活在不同的线程中这一事实有问题,因此我知道我可能只需要为连接找到一个合适的"连接类型"参数。最终,我发现只有使用Qt::DirectConnection才能使一切正常工作。

找到下面的简化代码。这里有一个关于一切应该如何工作的小描述。

  • 应用程序是主程序,它应该创建所有线程并启动它们。在这个简化版本中,它只需等待工人通过插槽"退出"完成作业。

  • Worker是执行其中一个线程任务的对象。在这个简化的例子中,我只是等了一段时间才完成计算。然后,worker发出一个指向应用程序实例的信号,然后允许应用程序实例等待所有线程并退出QCoreApplication。

我发现,如果我在第二次连接中不使用Qt::DirectConnection,那么工作线程的finished()信号不会触发线程的quit()槽,这意味着应用程序会挂起等待线程。

我的问题是:为什么会这样?既然这两个对象(工作线程和线程)属于不同的线程,我不应该使用QueuedConnection或其他什么吗?我认为DirectConnection应该只用于属于同一线程的对象。

main.cpp:

#include <iostream>
#include <QCoreApplication>
#include "program.h"
using namespace std;
int main(int argc, char **argv) {
  QCoreApplication app(argc, argv);
  Program *p = new Program;
  p->execute();
  app.exec();
  delete p;
}

program.h

#ifndef _PROGRAM_H_
#define _PROGRAM_H_
#include <QThread>
#include <QTimer>
#include "worker.h"
class Program: public QObject {
  Q_OBJECT
  private:
    Worker *worker;
    QThread *thread;
  public:
    void execute();
  public slots:
    void quit();
};
#endif // _PROGRAM_H_

程序.cpp

#include "worker.h"
using namespace std;
void Program::execute() {
  worker = new Worker();
  thread = new QThread;
  worker->moveToThread(thread);
  cout << "Connection established: "
        << connect(thread, SIGNAL(started()), worker, SLOT(process()))
        << endl;
  // slot not called if I remove the fifth parameter
  // or if I put Qt::QueuedConnection
  cout << "Connection established: "
        << connect(worker, SIGNAL(finished()), thread, SLOT(quit()),
                    Qt::DirectConnection)
        << endl;
  cout << "Connection established: "
        << connect(worker, SIGNAL(finished()), this, SLOT(quit()))
        << endl;
  cout << "Connection established: "
        << connect(worker, SIGNAL(finished()), worker, SLOT(deleteLater()))
        << endl;
  cout << "Connection established: "
        << connect(worker, SIGNAL(finished()), thread, SLOT(deleteLater()))
        << endl;
  thread->start();
}
void Program::quit() {
  cout << "waiting.." << endl;
  thread->wait();
  cout << "           .. I'm done!" << endl;
  cout << "quitting from all.." << endl;
  QCoreApplication::quit();
  cout << "           .. I'm done!" << endl;
}
#include "program_moc.cpp"

worker.h

#ifndef _WORKER_H_
#define _WORKER_H_
#include <QObject>
class Worker: public QObject {
  Q_OBJECT
  public slots:
    void process();
  signals:
    void finished();
};
#endif // _WORKER_H_

worker.cpp:

#include <iostream>
#include <unistd.h>
#include "worker.h"
using namespace std;
void Worker::process() {
  cout << "Worker::process() started" << endl;
  usleep(1000000);
  cout << "Worker::finished() being emitted" << endl;
  emit finished();
  cout << "Worker::process() finished" << endl;
}
#include "worker_moc.cpp"

编辑

遵循@ariwez的答案确实解决了这个特定简化示例中的问题,但在我现在添加的稍微复杂一点的示例中却没有。

在这个例子中,

  • 程序有自己的作业要通过使用QTimer定期执行。程序还有另一个QTimer,用于模拟用户退出程序,从而触发插槽Program::quit()的执行。

  • 工人执行自己的作业,直到他的退出标志设置为false。这是在Program::quit()中完成的。

与前面的例子一样,worker成功地完成了它的过程并发出finished()信号,该信号也应该与线程的quit()槽连接。但是,由于程序挂起等待线程,所以插槽不能以某种方式执行。与前面的例子不同,重新定位moveToThread过程并不能解决这个问题:当并且仅当我使用Qt::DirectConnection类型来连接Worker::finished()和QThread::quit()时,一切都能工作,我不明白为什么。

main.cpp:与上述相同

program.h:

#ifndef _PROGRAM_H_
#define _PROGRAM_H_
#include <QThread>
#include <QTimer>
#include "worker.h"
class Program: public QObject {
  Q_OBJECT
  private:
    QTimer *timer, *quittingTimer;
    Worker *worker;
    QThread *thread;
  public:
    ~Program();
    void execute();
  private slots:
    void quit();
    void update();
};
#endif // _PROGRAM_H_

program.cpp:

#include <iostream>
#include <QCoreApplication>
#include "program.h"
#include "worker.h"
using namespace std;
Program::~Program() {
  delete timer;
  delete quittingTimer;
}
void Program::execute() {
  timer = new QTimer();
  timer->setInterval(500);
  connect(timer, SIGNAL(timeout()), this, SLOT(update()));
  worker = new Worker;
  thread = new QThread;
  cout << "Connection established: "
        << connect(thread, SIGNAL(started()), worker, SLOT(process()))
        << endl;
  // doesn't work if I remove Qt::DirectConnection
  cout << "Connection established: "
        << connect(worker, SIGNAL(finished()), thread, SLOT(quit()),
                    Qt::DirectConnection)
        << endl;
  cout << "Connection established: "
        << connect(worker, SIGNAL(finished()), this, SLOT(quit()))
        << endl;
  cout << "Connection established: "
        << connect(worker, SIGNAL(finished()), worker, SLOT(deleteLater()))
        << endl;
  cout << "Connection established: "
        << connect(worker, SIGNAL(finished()), thread, SLOT(deleteLater()))
        << endl;
  worker->moveToThread(thread);
  timer->start();
  thread->start();
  // simulates user pressing key to close program
  quittingTimer = new QTimer();
  quittingTimer->singleShot(4000, this, SLOT(quit()));
}
void Program::quit() {
  cout << "timer->stop()" << endl;
  timer->stop();
  cout << "worker->quit()" << endl;
  worker->quit();
  cout << "thread->wait()" << endl;
  thread->wait();
  cout << "qcore->quit()" << endl;
  QCoreApplication::quit();
}
void Program::update() {
  cout << "Program::update() called" << endl;
}
#include "program_moc.cpp"

worker.h:

#ifndef _WORKER_H_
#define _WORKER_H_
#include <QObject>
class Worker: public QObject {
  Q_OBJECT
  private:
    bool quit_flag;
  public:
    void quit();
  public slots:
    void process();
  signals:
    void finished();
};
#endif // _WORKER_H_

worker.cpp:

#include <iostream>
#include <unistd.h>
#include <QThread>
#include "worker.h"
using namespace std;
void Worker::quit() {
  quit_flag = true;
}
void Worker::process() {
  quit_flag = false;
  while(!quit_flag) {
    cout << "Worker::process() is processing" << endl;
    usleep(300000);
  }
  cout << "Worker::finished() is being sent" << endl;
  emit finished();
  cout << "Worker::finished() is sent" << endl;
}
#include "worker_moc.cpp"

编辑2

重读@ariwez链接中的文章,我发现了第二个例子中的问题所在。问题是,在等待线程的QThread::finished()信号时,主事件循环被中断,因此Worker::finishedd()信号无法调度到QThread::quit()插槽中。所以,是的,基本上我自己陷入僵局。

在连接之前移动ToThread,这就是为什么所有东西都在一个线程中。

我想我知道问题出在哪里了。

你的directConnection,这是一件非常糟糕的事情,因为它的行为就像一个中断——立即执行。但正是因为它能立即执行,所以它才适用于您的情况。当线程关闭时,它会发出quit()信号。。。。然后通常它会"退出",但在direcetConnection中,它会立即执行slot finished(),然后退出。

如果你有一个QueuedConnection,那么它会向队列发出信号,然后完成"退出"。然而,一旦线程停止,线程队列就永远不会得到服务,因此您的finished()函数永远不会运行。

也许更好的方法是连接到你的对象finished(),在那里发送信号,然后让线程中的对象停止线程本身。(即,不要远程停止线程,停止对象,这反过来又会停止其线程)。

相关内容

最新更新