如何使方法发送信号并在返回之前等待插槽?

Posted

技术标签:

【中文标题】如何使方法发送信号并在返回之前等待插槽?【英文标题】:How to make a method send a signal and wait for the slot before returning? 【发布时间】:2016-11-22 09:34:35 【问题描述】:

我有一个类ConnectionManager,其方法get_wifi_ssids() 必须返回一个SSID 列表。问题是要获取这些 SSID 需要使用信号和插槽,但我无法找到在不先退出方法的情况下检索该信息的方法。

这是使用的类的层次结构,从最低到最高。

/** Controls wireless network card by commanding a software component "connman" via DBus. */
class WifiController : QObject 
Q_OBJECT

public:

    void scan();


/** Low level interface to network interfaces. */
class NetworkController : QObject 
    Q_OBJECT

public:

    void scan_for_wifi() 
        wifi_controller.scan();
        // When scan is finished it sends the
        // NetworkTechnology::scanFinished signal.
    

    // Gets info from cache. This cache is updated when a `scan()` happens.
    QList<AccessPointInfo> get_available_access_points;

private:
    WifiController wifi_controller;


/** High level interface to network interfaces. */
class ConnectionManager 
public:
    QList<QString> get_wifi_ssids() 
        netCtrlr.scan();
        // PROBLEM HERE: How do I wait for the `scanFinished` signal here, then
        // continue execution and return the SSIDs from the recently-updated
        // cache?

        QList<AccessPointInfo> APs  netCtrlr.get_available_access_points() ;
        QList<QSitrng> ssids  parseAPInfo(APs) ;
        return ssids;
    

private:
    NetworkController netCtrlr;

我的整个应用程序都在一个线程中。 “connman”由WifiConroller 通过 DBus 命令,它是一个单独的进程,因此显然在一个单独的线程中。 GUI 在单独的进程中运行,我的应用通过 DBus 与其通信。

QEventLoop 中的 cmets 表示,this answer 中的 cmets 表示,QEventLoop 是一个糟糕的解决方案,因为它不适合用于生产,而更像是一种黑客攻击。

【问题讨论】:

您真的必须返回来自get_wifi_ssids() 的列表吗?当它们可用时,为什么不在信号中发出列表呢?如果你想返回它们,你必须以某种方式在函数内部停止,直到列表可用,或者通过启动嵌套事件循环(这不鼓励),或者通过整个线程阻塞(这更不鼓励)。 我建议在列表可用时发出信号。或者将列表存储在您的类中,并为您的观察者发出信号以从您的类中获取最后一个可用结果(类似于QIODevice::readyRead() 信号的工作原理)。 @Mike 嗯,这是个好建议。但是为什么你说阻塞线程是一个坏主意(即使用自旋锁等到扫描完成)?我没有 GUI 线程(它在单独的进程中运行,我的应用通过 DBus 与它对话)。 抱歉,我错过了您的线程中没有 GUI 的部分。如果是这样,你可能会阻塞你的事件循环一段时间(不要太长,因为你毕竟是在一个事件驱动的系统中)。但是为什么不编写这个类以便它可以在任何应用程序中使用呢?如果您选择了阻塞方式,您将被绑定在非 GUI 线程中使用此组件。 @Dee 阻塞是个坏主意,因为阻塞时不会交换 dbus 数据。这也是一个坏主意,因为世界是异步的,而您只是为了迎合设计中的不合时宜而浪费了一个线程。一旦您学会了为异步现实世界编写代码,您就可以将它带到重要的 GUI 中。但无论如何,线程都是沉重而昂贵的资源。 【参考方案1】:

由于扫描操作是异步的,因此您不能真正拥有扫描 SSID 并返回它们的方法,因为等待扫描完成是一个阻塞操作。阻塞操作会阻止事件循环工作,并且会阻止处理信号信息。

您可以在get_wifi_ssids 方法中使用本地事件循环,但这会阻止应用程序的其余部分工作。如果 WiFi 扫描有任何挂断,程序将在此期间冻结。

相反,重新设计该类,使其在需要时开始扫描,get_wifi_ssids 返回有关接入点的最新信息。

【讨论】:

感谢您的建议。但是那个设计会是什么样子呢?我的问题中的用例是当用户要求我的应用程序使用 SSID 和密码连接到特定 AP 时。在尝试连接之前,我必须扫描 AP 并检查该 SSID 是否存在,否则中止请求。所以检查取决于扫描是否完成。 如果您在用户提供 SSID 和密码之前没有扫描接入点,则程序需要进入扫描完成并锁定界面的阶段。例如,您可以告诉连接管理器缓存 SSID 和密码,并在扫描完成时触发检查。 接口在另一个进程中运行,我的应用通过DBus与它通信,所以它不需要被锁定(你的意思是被阻止对吗?)。即使这样,用户单击“连接”也会触发“扫描阶段”,并且检查 SSID 是否存在仍然取决于扫描是否完成(以获取最新信息)。所以我不确定你的建议是什么 啊。我错过了 GUI 在另一个进程中的部分。然后你也可以在扫描完成时让程序休眠。 我最近添加了那部分,我不知道它是否相关。我假设您的意思是使用自旋锁来知道何时唤醒。感谢您的帮助【参考方案2】:

你可以使用本地的QEventLoop:

QList<QString> get_wifi_ssids() 
    QEventLoop event;
    // Stop event loop on signal
    connect(&netCtrlr, SIGNAL(scanFinished()), &event, SLOT(quit()));
    netCtrlr.scan();

    // run event loop
    event.exec();

    QList<AccessPointInfo> APs  netCtrlr.get_available_access_points() ;
    QList<QString> ssids  parseAPInfo(APs) ;
    return ssids;

【讨论】:

感谢您的帮助,但QEventLoop 不适合在生产环境中使用,更像是一种 hack(请参阅此内容的 cmets(answer 了解更多信息)。 链接答案中的链接不再起作用,但我认为他们指向了这样的文章:delta.affinix.com/2006/10/23/nested-eventloopsThere 使用事件循环的安全情况.所以问题是,您的应用程序是如何设计的?或者,您还可以将进程标志传递给 event.exec() 调用 但我同意 Teemu Piippo 的观点,即 GUI 应用程序不应在这种情况下阻塞。重新考虑进行无块设计。 @Dee ,你说得对,应该避免嵌套事件循环,我认为主要原因是让你进入新事件循环的事件可能会再次发生(从而开始另一个新的事件循环)调用堆栈上的事件循环)。您可能对代码中的这种情况没有做好准备(您需要正确处理reentrancy)。看看this question。

以上是关于如何使方法发送信号并在返回之前等待插槽?的主要内容,如果未能解决你的问题,请参考以下文章

在代码中连接信号和插槽并在 QtCreator“信号和插槽编辑器”窗口中设置它们有啥区别?

BOOST 如何在一个线程中发送信号并在另一个线程中执行相应的插槽?

如何获取从无关信号传递到插槽的值?

Qt 信号和插槽发送结构数组

QT 信号和槽函数签名

Qt 拾遗 003 disconnect