【问题标题】:Qt::QueuedConnection invoke at most once per event loop iterationQt::QueuedConnection 每次事件循环迭代最多调用一次
【发布时间】:2012-10-09 16:16:37
【问题描述】:

考虑以下情况:

class Foo : public QObject {
  Q_OBJECT
  public:
    void set_A(int a) { emit updated(this); } 
    void set_B(int b) { emit updated(this); }
  signals: 
    void updated(Foo*);
}

Foo f;
connect(&f, SIGNAL(updated(Foo*)), something, SLOT(do_something_heavy(Foo*)), Qt::QueuedConnection)

void bar() { 
  f.set_A(5); f.set_B(6);
}

如何确保只有一个信号到达do_something_heavy() 呼叫?

我希望能够使用set_A() 并调用do_something_heavy(),但是在同时调用set_A 和set_B 的情况下,我不想do_something_heavy() 两次。

我可以为该特定发送方/接收方对取消所有剩余未完成信号的队列吗?最好在emit,而不是在接收,但这只是为了简洁和封装 - 我希望updated(Foo*) 表示需要更改本地接收器的状态,如果连接排队,语义就是这样我不需要更新发生两次。

【问题讨论】:

    标签: qt signals signals-slots slot


    【解决方案1】:

    我发现使用 QTimer 来组合信号调用很有用。

    Foo f;
    QTimer timer;
    timer.setInterval(0); // this could be set to whatever.
    timer.setSingleShot(true);
    
    connect(&f, SIGNAL(updated()),
            &timer, SLOT(start()));
    connect(&timer, SIGNAL(timeout()),
            something, SLOT(do_something_heavy()));
    

    【讨论】:

      【解决方案2】:

      对于这种情况,我可以想到三种不同的选择。所有选项都不需要对连接进行排队,因为它们保证信号只发出一次。

      选项 1:定义类似 flush() 方法

      flush 方法随后会发出更新后的信号,而 setter 方法则不会。客户端代码必须手动调用 flush()。

      选项 2:程序空闲时发出信号

      只有在 延迟更新 是可以的情况下,这才是一个解决方案。这意味着,只有当您的应用程序空闲时,才会发出信号。但是由于您使用排队连接,因此已经是这种情况了。

      这是高度动态的,您甚至可以多次更改这两个参数,而昂贵的更新操作只被调用一次。

      要实现这一点,在 setter 方法中,您只需将私有布尔变量 changed 设置为 true,然后将单次计时器(超时为零)启动到 Foo 类中的特殊插槽,假设插槽 emitUpdated() .请注意,单次计时器将像调用 setter 方法一样频繁地调用您的插槽,因此插槽必须关心这一点。它只是检查changed 是否为真,发出真实信号并将设置更改为假。所以信号只会发出一次。

      请注意,这已经排队了!单发计时器将被放入事件队列(多次)并调用 emitUpdated 以发出一次实际信号。因此,您可能希望直接连接到此信号以避免排队连接带来的双重惩罚。

      选项 3:假设每个客户端代码都以相同的顺序调用设置器

      这会带来最佳性能。确保您始终调用setA,然后调用setB,并仅在setB 中发出信号。只有发布代码的一个选项(做出一些断言来验证这个“协议”),如果你总是想调用这两种方法。

      【讨论】:

      • 是的。选项 2 与我自己想出的类似,只是使用 QMap 版本的“已更改”,因为我想要调用者/被调用者语义 - 我希望每个调用者调用一次 do_something_heavy(),只是不适合每个调用者的方法 - 所以信号处理实际上是在每个接收器端......这很丑陋。我正在认真考虑探索 QSignalMapper 并将其子类化?
      • 可能值得一试,但在开始编码之前你应该考虑清楚,因为它会变得有点复杂。 QSignalMapper 可能在这里根本不起作用。我现在太累了,无法考虑;)
      • 我认为这是唯一的出路 - 'do_something_heavy()' 函数负责 GUI 更新(并位于 GUI 线程中),以防其中一个工作线程决定更新某些共享状态- 所以我希望它处理更改,但如果 GUI 循环过载,则只处理一次。
      • 我真正希望的是类似于“dequeueAllFrom(QObject *caller)”API 调用的内容(假设队列发生在接收 QObject 中)......但要求太多了。
      【解决方案3】:

      你可以做的是,断开 do something_heavy() 中的插槽。

      即当你开始执行 do something_heavy 时,断开插槽。执行完成后,重新连接插槽。这样,在执行过程中,不会再处理任何信号。虽然会发出信号,但由于没有连接相应的插槽,因此不会再次执行。

      【讨论】:

      • 谢谢,这是一个想法 - 但我想知道这是否会清除当前未完成的队列 - 我确实希望收到 new 信号,如果它们在执行繁重的操作时到达,但我不想在事件循环的同一迭代中以几份副本从同一发送者接收相同的信号 - 一个就足够了。
      • 在这种情况下,您可以在发送信号后断开特定发送者的连接,以便其他人仍然可以发送信号。
      • 我不确切知道 QT 如何处理信号队列,我认为这是特定于平台的逻辑。可能是你试图摆弄信号队列。
      猜你喜欢
      • 1970-01-01
      • 2014-10-11
      • 2021-05-27
      • 2023-02-03
      • 1970-01-01
      • 1970-01-01
      • 2021-02-15
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多