从Qt docs获取示例:一个创建一个控制器和一个worker,将worker移动到特定的线程并启动线程。启动/停止触发通过信号/插槽完成。
现在假设doWork
方法取决于某些参数(例如中间结果的输出速率(以某种可视化形式)),并且想要通过用户界面调整该参数,而函数是运行。
如果使用信号/插槽机制(在接口和工作器之间),这将不起作用,因为工作程序的接收槽将在运行事件循环之后排队,因此将在doWork
完成后执行。
我提出了以下解决方案:
方法1:
在public
中创建参数Worker
并直接通过界面进行更改。
class Worker : public QObject
{
Q_Object
public:
int parameter;
...
}
Interface::updateParameter(int p) { worker_instance.parameter = p; }
方法2:
将参数移动到接口本身或一些额外的类(生活在除工作者之外的另一个线程中),并让工作人员在每次需要时加载它。
class Interface : public QWidget
{
Q_Object
private:
int parameter;
public:
int getParameter() { return parameter; }
...
}
Worker::doWork() {
...
interface_instance.getParameter(); // load parameter;
...
}
不是将参数放在接口中,而是可以将它放在另一个类中,并通过信号/插槽机制与该类进行交互。
关于易用性我绝对更喜欢方法1.它还使事物保持在实际所属的位置。 方法2似乎并没有提供优于方法1的优势。 有什么意见吗?
还有什么不同的方法来处理这样的事情吗?例如,观看动画,我想有人想要启用动画速度的调整,这在某种程度上需要钩子进入动画过程。
您对此主题有任何想法或知识吗?
答案 0 :(得分:1)
如果使用信号/插槽机制(在接口和工作者之间) 这不会起作用,因为工人的接收槽将排队 在运行事件循环之后,因此将在doWork之后执行 结束。
这不是必然的情况,你可以使doWork()
无阻塞,你做一部分工作,然后让事件循环循环,允许与其他线程通信,你可以使用排队连接来转移这样的数据,然后做另一个工作步骤,另一个循环等等。这使您可以监视进度,暂停,取消工作以及在此期间来回传输数据。
阻止与非阻塞工作者:
main main
| worker | worker
|------>| |------>|
| | | |
| | |<----->
| | | |
| | | |
|<------| |<----->
| | |
| |<------|
| |
Here我已经设置了一个示例,将工作分解为步骤并运行这些步骤,而不会阻止工作线程允许通信通过。概念是相同的 - 将工作拆分为工作单元,跟踪工作对象中的状态,使用带参数的信号和插槽进行控制和数据传输。
请注意不要过度使用排队连接,因为与直接连接相比,它们非常慢,如果您充斥事件循环,实际上会丢失性能并且应用程序可能会挂起。考虑到目标通常是视觉或用户控制反馈,尽量不要每秒发出太多排队连接。像每秒30这样的东西是响应式用户界的最佳选择,但你甚至可以用更少的东西。
答案 1 :(得分:1)
这听起来有点像你想在这里进行线程同步,即互斥和线程锁定/解锁。
因此,在您的代码中,您将声明一个互斥变量(如果您有许多工作者,并且您的参数是特定于工作者的,那么它应该在工作者类中公开存在,否则如果它是“单例”参数,则将其粘贴在main中)。这实际上是一个全局变量。您还需要一个作为参数的全局变量。
然后在你的doWork()函数中:
Worker::doWork() {
...
// Get the parameter value
g_mutex.lock();
// take copy of the global parameter (m_ is member and g_ is global)
m_param = g_param;
g_mutex.unlock();
...
}
然后,您需要在任何地方实际设置/更改参数:
// Set the parameter value
g_mutex.lock();
g_param = ...;
g_mutex.unlock();
这可以确保在您尝试读取参数时不会覆盖/修改参数(如果您感到困扰)。这样做是很好的线程安全的做法。但是在“设置”方面使用互斥锁更重要。
你可以在读取时不使用互斥锁,因为你可能并不真正关心某人同时写入它(如果它的单个指令像POD一样写入),但如果它是一个结构,那么你可能会关心。
另一种方法是重新编写代码,以便你的工作单位更小(我认为像@ddriver那样......似乎是个好主意。
答案 2 :(得分:0)
这取决于你的工作的性质。它是否会消耗100%的CPU,或者它是基于事件的,是由定时事件引发的?你提到了可视化,这意味着它更像是一个定时事件,你唤醒你的线程,处理/更新数据,然后睡觉,直到它呈现下一帧为止。
如果是这种情况,您确实可以使用信号槽来更新数据。为此,您不要实现run(),因此使用默认实现(使用自己的事件循环)。在start()函数中,然后排队QTimer事件以调用doWork(),并调用父start()。事件循环启动,调用你的doWork(),你处理你的数据(按照你自己的步调 - 这不是GUI线程,你没有阻止任何东西),并在doWork结束时再次排队另一个QTimer事件,并返回到事件循环。
由于它正在运行事件循环,因此在这种情况下所有信号/插槽处理都将起作用。更重要的是,排队的连接信号/插槽根据定义是同步的,这意味着您不需要同步数据访问。