QT插槽没有在主线程上被调用

QT slot not getting called on main thread

从我的 QT GUI 应用程序中插槽的线程上下文(按下按钮时),我试图启动一个工作线程以使用 CPU 密集的结果更新 GUI 的其他部分计算 - 这些结果将更新 table 或 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.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);
};

它不起作用的原因是您的代码中存在严重缺陷:您试图发出对局部变量的引用,以便在不同线程的槽中处理。那是灾难的根源。

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

void progressUpdate(MMRTimedRecord val);

也就是说你的MMRTimedRecord必须是可复制的,相应的,你的slot也必须是按值接受的。为什么信号 progressUpdate(const MMRTimedRecord&) 和插槽 updateGUIWidget(const param& rParam); 不匹配?

您可以查看此 以获得可能的解决方案。你可以做到

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

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

这真的很简单——您不应该手动管理任何线程:

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

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