如何等待多个信号?

2024-10-01 11:21:53 发布

您现在位置:Python中文网/ 问答频道 /正文

我正在使用PySide和Qt开发一个GUI测试库。到目前为止,当测试用例只需要等待一个条件发生(例如信号或超时)时,它可以很好地工作,但是我的问题是在继续数据验证之前必须等待多个条件发生。在

测试运行程序在自己的线程中工作,这样就不会对主线程造成太大的干扰。等待信号/超时发生在一个事件循环中,这部分工作得很好(简化示例):

# Create a  simple event loop and fail timer (to prevent infinite waiting)
loop = QtCore.QEventLoop()
failtimer = QtCore.QTimer()
failtimer.setInterval(MAX_DELAY)
failtimer.setSingleShot(True)
failtimer.timeout.connect(loop.quit)

# Connect waitable signal to event loop
condition.connect(loop.quit) # condition is for example QLineEdit.textChanged() signal

# Perform test action
testwidget.doStuff.emit() # Function not called directly, but via signals

# Wait for condition, or fail timeout, to happen
loop.exec_()

# Verify data
assert expectedvalue == testwidget.readValue()

等待必须是同步的,所以一个事件循环是可行的,但它不能用于多个信号。当然可以等待多个条件中的任何一个,但不是等待多个条件/信号全部发生。有什么建议可以继续吗?在

我在考虑一个helper类,它统计接收到的信号数,然后在达到所需计数后发出ready()-信号。但这真的是最好的方法吗?助手还必须检查每个发送方,以便只考虑特定信号的一个“实例”。在


Tags: toloopeventforsignal信号connecttimeout
3条回答

我个人会把所有必要的信号连接到相应的信号处理程序,也就是aka。插槽。在

他们都会标记他们的发射是“完成”的,并且可能会检查整体情况是否“完成”,在每个信号处理程序设置自己的“完成”之后,可能会有一个全局“完成”检查,如果这足够,他们将发出“全局完成”信号。在

然后,您还可以最初连接到“全局完成”信号,当相应的信号处理程序被触发时,您将知道该操作已完成,除非同时条件发生更改。在

理论设计完成后,你会得到这样的东西(伪代码)

connect_signal1_to_slot1();
connect_signal2_to_slot2();
...
connect_global_done_signal_to_global_done_slot();

slotX: mark_conditionX_done(); if global_done: emit global_done_signal();
global_done_slot: do_foo();

您可能还可以通过只有两个信号和插槽来简化,即:一个用于根据传递的参数“标记”本地信号完成的本地done操作,然后还有“global done”信号和插槽。在

不同的是语义,是使用带一个信号和时隙的参数,还是使用没有参数的多个信号和时隙的参数,但原理上是相同的。在

<>这是一个非常简单的方法,在C++中,将是:

  1. 有一组(object,signal index)对,您希望得到信号。

  2. 在等待开始之前复制集合。

  3. 在插槽中,从复制的列表中删除(sender(),senderSignalIndex())元素。如果列表是空的,你就知道你完成了。

该解决方案的好处是可移植性:该方法在PysEnEm和 C++中都有效。在

在C++中,^ {}习惯地被封装在^ {< CD2>}或^ {CD3>}宏中的方法参数调用。这些宏在方法代码前面加上一个“0”、“1”或“2”,以指示它是可调用的方法、信号还是插槽。调用registerSignal时跳过此方法代码,因为它需要一个原始方法名。在

由于在registerSignal中调用的indexOfMethod需要一个规范化的签名,connect方法将其规范化。在

class SignalMerge : public QObject {
    Q_OBJECT
#if QT_VERSION>=QT_VERSION_CHECK(5,0,0)
    typedef QMetaObject::Connection Connection;
#else
    typedef bool Connection;
#endif
    typedef QPair<QObject*, int> ObjectMethod;
    QSet<ObjectMethod> m_signals, m_pendingSignals;

    void registerSignal(QObject * obj, const char * method) {
        int index = obj->metaObject()->indexOfMethod(method);
        if (index < 0) return;
        m_signals.insert(ObjectMethod(obj, index));
    }
    Q_SLOT void merge() {
        if (m_pendingSignals.isEmpty()) m_pendingSignals = m_signals;
        m_pendingSignals.remove(ObjectMethod(sender(), senderSignalIndex()));
        if (m_pendingSignals.isEmpty()) emit merged();
    }
public:

    void clear() {
        foreach (ObjectMethod om, m_signals) {
            QMetaObject::disconnect(om.first, om.second, this, staticMetaObject.indexOfSlot("merge()"));
        }
        m_signals.clear();
        m_pendingSignals.clear();
    }
    Q_SIGNAL void merged();
    Connection connect(QObject *sender, const char *signal, Qt::ConnectionType type = Qt::AutoConnection) {
        Connection conn = QObject::connect(sender, signal, this, SLOT(merge()), type);
        if (conn) registerSignal(sender, QMetaObject::normalizedSignature(signal+1));
        return conn;
    }
};

我最终实现了一个相当简单的helper类。它有一组用于等待信号,另一组用于接收信号。每个可等待的信号都连接到一个插槽。插槽将sender()添加到就绪集合,一旦集合大小匹配,就发出ready信号。在

如果有人感兴趣,我最后要做的是:

from PySide.QtCore import QObject, Signal, Slot

class QMultiWait(QObject):
    ready = Signal()

    def __init__(self, parent=None):
        super(QMultiWait, self).__init__(parent)
        self._waitable = set()
        self._waitready = set()

    def addWaitableSignal(self, signal):
        if signal not in self._waitable:
            self._waitable.add(signal)
            signal.connect(self._checkSignal)

    @Slot()
    def _checkSignal(self):
        sender = self.sender()
        self._waitready.add(sender)
        if len(self._waitready) == len(self._waitable):
            self.ready.emit()

    def clear(self):
        for signal in self._waitable:
            signal.disconnect(self._checkSignal)

clear函数几乎没有必要,但允许重用类实例。在

相关问题 更多 >