PyQt 信号:在断开连接()之后,触发的信号仍然可以工作吗?

Posted

技术标签:

【中文标题】PyQt 信号:在断开连接()之后,触发的信号仍然可以工作吗?【英文标题】:PyQt signals: will a fired signal still do its job after a disconnect()? 【发布时间】:2018-11-01 06:14:56 【问题描述】:

我正在处理一个应用程序,其中许多信号被触发,然后重新连接。我将详细解释该应用程序是如何工作的,以及我的困惑从哪里开始。

 

1。重新连接信号

在我的应用程序中,我经常重新连接信号。为此,我将使用以下静态函数,取自这篇文章中@ekhumoro 的答案(并稍作修改):PyQt Widget connect() and disconnect()

def reconnect(signal, newhandler):
    while True:
        try:
            signal.disconnect()
        except TypeError:
            break
    if newhandler is not None:
        signal.connect(newhandler)

 

2。应用程序

想象函数emitterFunc(self) 循环遍历对象列表。在每次迭代中,该函数将mySignal 连接到一个对象,触发信号,然后在下一个迭代步骤开始时再次断开mySignal。发射的信号还携带一些有效载荷,例如对象Foo()

 

编辑:

上面显示的设计简化了很多。在最终的设计中,信号发射器和接收槽可能在不同的线程中运行。

由于会导致我们误入歧途的原因,我不能一次连接所有对象,发出信号,最后断开所有对象。我必须一个接一个地循环,执行连接-发射-断开过程。

再一次,由于可能导致我们太过分的原因,我不能直接调用这些插槽。

 

3。 Signal-Slot 机制的心理形象

随着时间的推移,我对信号槽机制的工作原理有了一个心理印象。我想象一个 Signal-Slot 引擎 吸收所有发射的信号并将它们放入队列中。每个信号都在等待轮到它。当时间准备好时,引擎将给定的信号传递给适当的处理程序。为了正确地做到这一点,引擎有一些“簿记”工作,以确保每个信号最终都在正确的插槽中。

 

4。 Signal-Slot 引擎的行为

假设我们处于第 nth 次迭代步骤。我们将self.mySignal 连接到object_n。然后我们用它的有效载荷发射信号。几乎在这样做之后,我们立即断开连接并建立到 object_n+1 的新连接。在我们断开连接的那一刻,触发的信号可能还没有完成它的工作。我可以想象 Signal-Slot 引擎的三种可能行为:

[选项 1] 引擎注意到连接已断开,并从其队列中丢弃 sig_n

[OPTION 2] 引擎注意到与另一个处理程序的连接重新建立,并将sig_n 发送到 object_n+1 的处理程序(一旦它到达队列的前面)。

[选项 3] 引擎不会更改 sig_n 的任何内容。当被触发时,它是为 object_n 的处理程序而设计的,这就是它的结束位置。

 

5。我的问题

我的第一个问题现在已经很明显了。什么是正确的信号槽引擎行为?我希望这是第三种选择。

作为第二个问题,我想知道给定的心理形象在多大程度上是正确的。例如,我可以依靠信号按顺序从队列中出来吗?这个问题不太重要——它对我的申请当然不是至关重要的。

第三个问题与时间效率有关。重新连接到另一个处理程序是否耗时?一旦我知道第一个问题的答案,我将继续构建应用程序,我可以自己测量重新连接时间。所以这个问题不是那么重要。但如果你知道答案,请分享:-)

【问题讨论】:

【参考方案1】:

我将从您的第二个问题开始,说您的心理形象部分正确,因为涉及到队列,但并非总是如此。发出信号时,有三种可能的方式调用连接的插槽,其中两种使用事件队列(QMetaCallEvent 即时实例化并使用QCoreApplication 的方法postEvent 发布,如果您愿意,事件目标是插槽持有者或信号接收器)。第三种情况是直接调用,所以发出信号就像调用槽一样,没有任何东西进入队列。

现在回到第一个问题:在任何情况下,当发出信号时,都会遍历一个连接列表(属于信号发射器),并使用以下方法一个接一个地调用插槽上面提到的三种方法。每当建立或断开连接时,列表都会更新,但这必然发生在信号发出之前或之后。简而言之:在发出信号后成功阻止对已连接插槽的调用的机会很小,至少不会中断与disconnect() 的连接。所以我会将[OPTION 3] 标记为正确。

如果您想进一步挖掘,请从ConnectionType enum documentation 开始,其中很好地解释了三种基本的连接类型(直接、队列和阻塞队列)。 连接类型可以指定为QObject 的方法connect 的第五个参数,但是,正如您将从上面链接的文档中了解到的那样,Qt 本身通常会选择连接最适合情况的类型。剧透:涉及线程:)

关于第三个问题:我手头没有要展示的基准测试,所以我将给出一个所谓的主要基于意见的答案,即开始的那种答案恕我直言。我认为 signal/slot 领域是 keep-it-simple 规则确实规则的领域之一,而您的 reconnect 模式似乎使事情变得比需要的复杂得多。正如我上面所暗示的,当建立一个连接时,一个连接对象被附加到一个列表中。当信号发出时,所有连接的插槽都会以某种方式被调用,一个接一个。因此,与其在循环中的每个循环中断开/重新连接/发出,为什么不先连接所有项目,然后发出信号,然后将它们全部断开?

我希望我的(冗长的,也许是 tldr)回答有所帮助。很好读。

【讨论】:

嗨@p-a-o-l-o,非常感谢你,你的回答很有帮助!我有几个问题---(1)你说有三种可能的方式来调用连接的插槽。我只能在你的回答中读到两个:一个是QMetaCallEvent,一个是直接的。第三个是什么? --- (2) 你说有非常少的机会 事情出错,所以你将我的[OPTION 3] 标记为正确。当你说机会很少时,我觉得有点不敢依赖它。你怎么看? --- (3) 我的问题中的应用程序非常简化。在那个简化版本中,确实可以先连接所有项目,然后发出信号,然后将它们全部断开。但请相信我,在 real 应用程序中,这不是一个选项。由于会导致我们误入歧途的原因,我需要在每个周期断开/重新连接/发射。 (1) QMetaCallEvent 在排队连接或阻塞排队连接的情况下发布(其中信号线程阻塞直到槽返回)。 (2) 在信号发出后,很少有机会阻止槽被调用,使用disconnect 完全没有机会。 (3) 显然,我想您选择该设计并非偶然。我只是想确保您理解连接 意味着添加到连接列表。无论如何,如果您的应用程序是单线程的,或者您的调用列表中不涉及线程,我会采用传统模式,即直接调用对象插槽(方法)并完全断开连接。同样,我只能猜测。无论如何,如果我以某种方式做出贡献,我很高兴。 谢谢先生。你的贡献肯定比你想象的要多:-)。我还有一个问题。您说“在发出信号后阻止调用槽的机会很少,当然也没有机会使用disconnect。”。我很高兴听到这个。但是reconnect 呢?我的意思是,是否有可能调用 wrong 插槽(因为坐在队列中时,信号已连接到另一个插槽)。引用上面我的问题中的示例,sig_n 是否可以以 object_n+1 的处理程序结束?我只是想确保这不会发生;-)

以上是关于PyQt 信号:在断开连接()之后,触发的信号仍然可以工作吗?的主要内容,如果未能解决你的问题,请参考以下文章

PyQt 视频帧更新信号(每个视频帧后的触发函数)

Python需要disconnect信号吗

在循环中连接 PyQt4 中的插槽和信号

PyQt5快速上手基础篇2-按钮控制LCD屏显示

C++ GUI Qt的建立连接,信号和槽

关闭窗口时如何触发pyqt5信号[重复]