我们说我有一个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::async
或QtConcurrency
)。
答案 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
进行通信。