PyQt信号:在断开连接后,已触发的信号是否仍会执行其任务?

3
我正在处理一个应用程序,许多信号被触发后会进行重新连接。我将详细解释应用程序的工作原理,以及我的困惑之处。
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()

enter image description here

编辑:

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

  • 由于种种原因,我不能一次性连接所有对象,然后发射信号,最后断开它们所有的连接。我必须逐个地循环它们,执行连接-发射-断开的过程。

  • 同样出于一些原因,我不能直接调用这些插槽。

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

随着时间的推移,我已经建立了一个关于Signal-Slot机制如何工作的心理形象。我想象一个Signal-Slot引擎吸收所有触发的信号并将它们放在队列中。每个信号都等待着自己的时机。当时机成熟时,引擎将给定的信号传递给相应的处理程序。为了正确地做到这一点,引擎需要一些“簿记”工作,以确保每个信号都最终到达正确的插槽。

enter image description here

4. Signal-Slot引擎的行为

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

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

  • [选项2] 引擎注意到已重新连接到另一个处理程序,并在 sig_n 到达队列前端时将其发送到 object_n+1 的处理程序。

  • [选项3] 引擎不会为 sig_n 更改任何内容。触发时,它是用于 object_n 处理程序的,也将在那里结束。

 

5. 我的问题

我的第一个问题现在已经非常明显了。正确的信号-槽引擎行为是什么?我希望它是第三个选项。

作为第二个问题,我想知道给定的心理图像在多大程度上是正确的。例如,我可以依赖信号按顺序从队列中出来吗?这个问题不太重要——对于我的应用程序来说肯定不是至关重要的。

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

1个回答

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

现在来回答第一个问题:无论如何,在发出信号时,都会遍历连接列表(属于信号发射器),并使用上面提到的三种方法之一依次调用槽。每当建立或断开连接时,列表将被更新,但这必须在信号发出之前或之后发生。简而言之:在信号发出后阻止对连接的调用成功的机会很小,至少不能使用disconnect()断开连接。因此,我会将[OPTION 3]标记为正确的。

如果你想深入了解,可以从ConnectionType enum documentation开始,其中详细解释了三种基本连接类型(直接、排队和阻塞排队)。作为QObject的方法connect的第五个参数,可以指定连接类型,但是,正如上面链接的文档所述,很多时候Qt会自行选择最适合情况的连接类型。剧透:涉及线程 :)
关于第三个问题:我手头没有基准测试可以展示,所以我会给出一个所谓的基于个人观点的答案,这种答案通常以IMHO开头。我认为信号/槽领域是那些遵循简单规则的领域之一,你的重新连接模式似乎使事情比必要的复杂。正如我上面提到的,当建立连接时,会将连接对象附加到列表中。当信号发射时,所有连接的槽都将被依次调用。因此,在您的循环中每个周期断开/重新连接/发射信号,为什么不先连接所有项,然后发射信号,最后再断开它们呢?
我希望我的(冗长且可能需要阅读太多)回答有所帮助。祝阅读愉快。

嗨@p-a-o-l-o,非常感谢,你的回答非常有帮助!我有几个问题---(1)您说有三种可能的方法来调用已连接的槽。在您的答案中,我只能读到两种:使用QMetaCallEvent和直接调用。第三种是什么?---(2)您说事情出错的可能性非常小,所以您将我的[OPTION 3]标记为正确。当您说“可能性非常小”时,我有点不愿意依赖它。你怎么看? - K.Mulier
(1)在排队连接或阻塞排队连接(其中信号线程会阻塞直到插槽返回)的情况下,会发布QMetaCallEvent事件。 (2)几乎没有机会防止在发出信号后调用插槽,并且使用disconnect根本无法做到这一点。 - p-a-o-l-o
(3)显然,我想你不是偶然选择了那个设计。我只是想确保你理解“连接”意味着“添加到连接列表”。无论如何,如果你的应用程序是单线程的或者线程不涉及你的调用列表,我会采用传统的模式,即直接调用对象插槽(方法)并完全放弃连接。再次说明,我只能猜测。无论如何,如果我在某种程度上有所贡献,我很高兴。 - p-a-o-l-o
如果是直接连接,插槽将在断开连接之前被调用。如果是排队连接,则发布的事件最终将到达其接收器,并且它将是正确的,无法通过调用断开连接来更改事件目标,它们只是两个不同的世界。所以,可以百分之百确定 :) - p-a-o-l-o
嗨 @ekhumoro,非常感谢您的帮助。我刚刚添加了一个 EDIT 来澄清我的意图。这个 EDIT 也解释了为什么我的当前示例看起来有点尴尬(因为它过于简化,试图将焦点放在信号-槽机制上)。很抱歉我之前没有澄清。 - K.Mulier
显示剩余3条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接