在另一个线程上发出QObject信号的正确方法?

时间:2017-08-16 15:11:51

标签: c++ qt qt5

我们说我有一个slave对象,它存在于另一个线程中。我想告诉它在那个线程上做A,B,C。我可以想到3种方法:

(1)使用QTimer::singleShot

(2)使用QMetaObject::invokeMethod

(3)创建另一个master对象并将其信号连接到slave

以下是一个例子:

class slave : public QObject
{
    QThread thread_;

    friend class master;
    void do_A(params);
    void do_B(params);
    void do_C(params);

public:
    slave() { thread_.start(); moveToThread(&thread_); }
    ~slave() { thread_.quit(); thread_.wait(); }

    void que_A(params) { QTimer::singleShot(0, [&](){ do_A(params); }); } // (1)

    void que_B(params) { QMetaObject::invokeMethod(this, "do_B", params); } // (2)
}

class master : public QObject // (3)
{
    Q_OBJECT

public:
    master(slave* s) { connect(this, &master::que_C, s, &slave::do_C); }

    void do_C(params) { emit que_C(params); }

signals:
    void que_C(params);
}

我担心的是:

(1)我在滥用QTimer

(2)使用字符串表示信号/插槽是qt4。 Qt5使用new syntax

(3)样板太多了。

与其他方法相比,是否认为任何方法更正确?或者有人能想出更好的方法吗?

请包括您的推理(不仅仅是意见)为什么应该选择一种方法而不是其他方法。

更新

在我的真实应用程序中,我有另一个类 - 让我们称之为owner - 它拥有多个slave个。 owner需要告诉不同的slave根据用户输入做不同的事情(A,B或C)。 slave是有状态对象,因此我看不到使用并发函数的简单方法(例如std::asyncQtConcurrency)。

3 个答案:

答案 0 :(得分:2)

好吧,我不会评论(1)和(2),但我必须说(3)是通常使用的。但是,我理解你对太多样板的关注。毕竟,创建一个单独的信号,比如doActionA(),通过QueuedConnection将其连接到某个真实的actionA(),最后发出它...太多的噪音和无用的动作。

实际上,它给你带来的唯一好处就是松耦合(你可以发出信号,不知道是否存在连接到它的插槽)。但是,如果我创建一个名为doActionA() 的信号,我当然知道<{1}}存在。那么问题就是开始提高&#34;为什么我要写所有这些东西?&#34;

与此同时,Qt提供了解决此问题的方法,使您能够将自己的事件发布到任何事件循环(并且您知道QThread有一个)。因此,实施一次,您不再需要编写大量actionA() connect内容。另外,我认为我的效率更高,因为总而言之,通过emit的每个插槽调用只是在事件循环中发布一个事件。

这里QueuedConnection将成员函数执行的事件发布到QObject所在的线程的事件循环中:

InvokeAsync

输出:

#include <QCoreApplication>
#include <QEvent>
#include <QThread>

#include <string>
#include <iostream>
#include <type_traits>
#include <functional>

template<typename T, typename R, typename ... Params, typename... Args>
void InvokeAsync(T* object, R (T::*function)(Params...), Args&&... args)
{
    struct Event : public QEvent
    {
        std::function<R ()> function;
        Event(T* object, R (T::*function)(Params...),  Args&& ... args)
            : QEvent{ QEvent::None },
              function{ std::bind(function, object, std::forward<Args>(args)...) }
        {
        }
        ~Event() { function(); }
    };

    QCoreApplication::postEvent(object, new Event{ object, function, std::forward<Args>(args)... });
}

struct Worker : QObject
{
    void print(const std::string& message, int milliseconds)
    {
        QThread::currentThread()->msleep(milliseconds);
        std::cout << message
                  << " from thread "
                  << QThread::currentThreadId() << std::endl;
    }
};

int main(int argc, char* argv[])
{
    QCoreApplication a(argc, argv);

    std::cout << "GUI thread " << QThread::currentThreadId() << std::endl;

    QThread thread;
    thread.start();

    Worker worker;
    worker.moveToThread(&thread);

    InvokeAsync(&worker, &Worker::print, "Job 1", 800);
    InvokeAsync(&worker, &Worker::print, "Job 2", 400);
    InvokeAsync(&worker, &Worker::print, "Job 3", 200);

    a.exec();

    return 0;
}

当你看到所有的工作都按照他们的调用顺序在不同的线程中完成。 Qt保证具有相同优先级的事件在发布时按顺序处理。

如果GUI thread 00000000000019C8 Job 1 from thread 00000000000032B8 Job 2 from thread 00000000000032B8 Job 3 from thread 00000000000032B8 存在于GUI线程中,也没关系。事件将在GUI事件循环中发布并稍后处理(这就是我称之为worker的原因)。

如果您发现任何错误或有任何评论,请写下评论,我们会弄清楚。

答案 1 :(得分:1)

我不确定我是否理解这个问题。 所以我将在这里解释如何在Qt中管理线程(以概括的方式)。

首先,QThread类并不是真正意义上的继承。 相反,做这样的事情:

class Slave : public QObject {
    Slave(QThread *thread) {moveToThread(thread);)
};

QThread thread;
Slave slave(&thread);

之后,您通常可以正常使用信号和插槽。

但是,如果你的目标只是在另一个线程中运行一个函数&#34;,那么QConcurrent可能是更好的方法吗?

答案 2 :(得分:1)

事实上,Qt documentation提供了一个非常好的示例,可能会回答您关于如何使用QThread的问题。它是以下内容:

Tweet

这里的想法很简单。你有一个在线程中运行的worker。您还拥有控制器,它基本上是您将工作提交给其他线程的主线程。线程将(线程安全地)在完成时发出@OneToMany(cascade=CascadeType.ALL, mappedBy="groupBase", targetEntity=Groups.class) @PrivateOwned private List groups = new ArrayList<>(); 信号。信号和插槽在这里是线程安全的,只要他们使用Qt::QueuedConnection进行通信。