QT插槽未在主线程上调用

时间:2018-10-08 20:55:00

标签: c++ qt signals-slots qthread

从QT GUI应用程序中插槽的线程上下文(按按钮时),我试图启动工作线程以使用CPU密集型计算的结果更新GUI的其他部分-这些结果将更新表格或类似Google的地图小部件-因此,这需要在安全地更新这些小部件的主QT应用程序线程中进行。

我遇到的问题是,除非将连接类型更改为updateGUIWidget,否则Qt::DirectConnection插槽永远不会被调用-在这种情况下,它会在辅助线程中被调用(在这种情况下,不安全地更新GUI)。我检查了每个连接调用的结果,它们都很好,看来某个地方的事件循环存在一些问题。我不确定是否需要将线程和辅助对象分配为主窗口的成员,或者是否可以从插槽中的堆栈变量中分配它。

void
mainwindow::on_importSimulatedFlight_clicked()
{
    // experimental worker thread model adapted from YouTube tutorial
    // by Giuseppe di Angelo https://www.youtube.com/watch?v=BgqT6SIeRn4
    // auto thread = new QThread;
    // note worker created in gui thread here - we will move its thread
    // affinity to 'thread' below before starting it.
    auto thread = new QThread;
    auto worker = new Worker;
    connect(thread, &QThread::started, worker, &Worker::doWork);
    // connect(worker, &Worker::progressUpdate, this, &mainwindow::updateGUIWidget, Qt::DirectConnection);
    connect(worker, &Worker::progressUpdate, this, &mainwindow::updateGUIWidget, Qt::QueuedConnection);
    connect(worker, &Worker::workDone, thread, &QThread::quit);
    connect(thread, &QThread::finished, worker, &Worker::deleteLater);
    // move worker to separate thread
    worker->moveToThread(thread);
    thread->start();
}

mainwindow在mainwindow.h中声明了一个插槽,如下所示:

class mainwindow : public QMainWindow
{
    Q_OBJECT
public:
    explicit mainwindow(QWidget *parent = Q_NULLPTR);
    ~mainwindow();
    ...
public slots:
    void on_importSimulatedFlight_clicked();
    void updateGUIWidget(const param& rParam);
    ...
}

并在mainwindow.cpp中实现,如下所示:

void
mainwindow::updateGUIWidget(const param& rParam)
{
... update widget components with rParam partial result here
}

我的工人如下:

class Worker : public QObject
{
    Q_OBJECT
public slots:
    void doWork() {
        const QString result;
        for (int i=0; i<5; i++) {
            const MMRTimedRecord foo;
//            QThread::sleep(1);
            emit progressUpdate(foo);
        }
        emit workDone(result);
    }
signals:
    void progressUpdate(const MMRTimedRecord&);
    void workDone(const QString &result);
};

3 个答案:

答案 0 :(得分:2)

它不起作用的原因是因为您的代码中存在严重缺陷:您试图发出对要在其他线程上的插槽中处理的局部变量的引用。那是灾难的秘诀。

使用Qt::QueuedConnection时,必须按值发出,如下所示:

void progressUpdate(MMRTimedRecord val);

这意味着您的MMRTimedRecord必须是可复制的,并且相应地,您的广告位还必须按值接受。为何信号progressUpdate(const MMRTimedRecord&)与时隙updateGUIWidget(const param& rParam);之间不匹配?

答案 1 :(得分:0)

您可以检查此answer以获得可能的解决方案。您可以

MainThreadEvent::post([&]()
  {
    // gui update stuff
  }
);

在插槽中执行主线程中的gui更新,但是可以肯定,这是一种粗略的方法。尽管如此,我一直都在做这样的事情。小心悬挂指针和引用(使用QPointer)...,因为发出的事件独立于发出对象。或者,使用计时器方法。

答案 2 :(得分:0)

这真的很容易-而且您不应该手动管理任何线程:

void Ui::slot() {
  QtConcurrent::run([this]{
    auto result = compute();
    QMetaObject::invokeMethod(this, [this, r = std::move(result)]{
      m_widget.setSomething(r);
    });
  });
}

您计算的数据类型应该是可移动的。