3

I have a class ConnectionManager with method get_wifi_ssids() that must return a list of SSIDs. The problem is that to get those SSIDs signals and slots need to be used, but I can't figure out a way to retrieve that information without first exiting the method.

Here is the hierarchy of the classes used from lowest-level to highest.

/** 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;
}

The entirety of my application is in a single thread. "connman" is commanded by WifiConroller via DBus, and it is a separate process so obviously in a separate thread. The GUI runs in a separate process and my app communicates with it via DBus.

A QEventLoop is a bad solution because it's not meant to be used in production and is more of a hack, according to the comments in this answer.

Community
  • 1
  • 1
DBedrenko
  • 4,871
  • 4
  • 38
  • 73
  • 1
    Do you really have to **return** the list from `get_wifi_ssids()`? why not emitting the list in a signal when they are available instead? If you want to return them, you'll have to somehow stop inside the function until the list is available, either by starting a nested event loop (and this discouraged), or by blocking by the whole thread (and this is even more discouraged). – Mike Nov 22 '16 at 12:27
  • 1
    I would suggest emitting a signal with the list when it is available. Or store the list in your class, and emit a signal for your observer to get the last available result from your class (something like how [`QIODevice::readyRead()`](https://doc.qt.io/qt-5/qiodevice.html#readyRead) signal works). – Mike Nov 22 '16 at 12:28
  • @Mike Hmm that's a good suggestion. But why do you say blocking the thread is a bad idea (i.e. using a spinlock to wait until the scan has finished)? I don't have a GUI thread (it's running in a separate process, and my app talks to it via DBus). – DBedrenko Nov 22 '16 at 12:50
  • Sorry, I missed the part that you don't have a GUI in your thread. If so, you may block your event loop for a little longer time (not too long, since you are in an event-driven system after all). But why not writing this class so that it can be used in any application? If you chose the blocking way, You are tied to using this component in a non GUI thread. – Mike Nov 22 '16 at 15:38
  • 2
    @Dee Blocking is a bad idea because no dbus data will be exchanged while you block. It's also a bad idea because world is asynchronous, and you're wasting a thread just to cater to an anachronism in the design. Once you learn to code for an asynchronous real world, you can carry it over to GUIs where it's important. But in any case, threads are heavy, expensive resources. – Kuba hasn't forgotten Monica Nov 22 '16 at 21:36
  • @KubaOber Isn't the incoming DBus communication queued? If I enter a heavy calculation function for 10ms and get some DBus communication in that time, is it ignored/lost? – DBedrenko Nov 23 '16 at 06:31

2 Answers2

5

Since the scanning operation is asynchronous, you cannot really have a method that scans the SSIDs and returns them, because waiting for the scan to finish is a blocking operation. Blocking operations prevent the event loop from working, and signal information to get processed.

You can have a local event loop inside the get_wifi_ssids method, but this will block the rest of the application from working. If there's any hangup on the WiFi scan, the program will freeze during it.

Instead, redesign the class so that it starts the scan whenever needed, and get_wifi_ssids returns the up to date information on the access points.

Teemu Piippo
  • 292
  • 1
  • 9
  • 1
    Thanks for the advice. But what would that design look like? The use case in my question is when the user asks my app to connect to a specific AP with SSID and password. Before I try to connect, I must scan for APs and check if that SSID is there, otherwise abort the request. So the check is dependent on the scan being finished. – DBedrenko Nov 22 '16 at 09:52
  • If you haven't scanned the access points before the user gives the SSID and password, the program needs to enter a phase where the scan is being done and the interface is locked out. For instance you can tell the connection manager to cache the SSID and password, and trigger the check when the scan is done. – Teemu Piippo Nov 22 '16 at 09:57
  • The interface is running in another process, and my app communicates with it via DBus so it doesn't need to be locked out (you mean blocked right?). Even then the "scan phase" is triggered by the user clicking "Connect", and checking if the SSID is present is still dependent on the scan being finished (to get the latest info). So I'm not sure what your suggestion is – DBedrenko Nov 22 '16 at 10:06
  • 1
    Ah. I missed the part where the GUI is in another process. Then you may as well sleep the program while the scan finishes. – Teemu Piippo Nov 22 '16 at 10:11
  • I added that part recently, I didn't know it was relevant. I assume you mean use a spinlock to know when to wakeup. Thanks for the help – DBedrenko Nov 22 '16 at 10:13
2

You can use a local 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;
}
Delgan
  • 18,571
  • 11
  • 90
  • 141
king_nak
  • 11,313
  • 33
  • 58
  • Thanks for your help, but `QEventLoop`'s are not meant to be used in production and are more of a hack (see comments of this ([answer](http://stackoverflow.com/a/3556525/797744) for more info). – DBedrenko Nov 22 '16 at 09:44
  • The links in the linked answer don't work anymore, but I think they pointed to some article like this: http://delta.affinix.com/2006/10/23/nested-eventloops/ There _are_ safe situations to use an event loop. So the question is, how is your application designed? Optionally, you can also pass process flags to the `event.exec()` call – king_nak Nov 22 '16 at 09:59
  • But I agree with Teemu Piippo that a GUI application should not block in such a case. Reconsider to make a block-free design. – king_nak Nov 22 '16 at 10:02
  • 1
    @Dee , You're right nested event loops should be avoided, the main reason in my opinion is that the event that made you enter the new event loop may happen again (and thus starting even another new event loop on your call stack). You may not be prepared to such situations in your code (You need to handle [reentrancy](https://en.wikipedia.org/wiki/Reentrancy_%28computing%29) correctly). Have a look at [this question](https://stackoverflow.com/q/35561182/2666212). – Mike Nov 22 '16 at 11:42