c++qtasynchronousdesign-patternsqfuture

How to design asynchronous wrapper returning values in Qt?


I wrote a wrapper class in Qt for an external executable. It has plenty of methods, and most of them:

The synchronous wrapper is pretty straight forward in this case:

class SyncWrapper {
public:
    // Values are fetched *synchronously* in these methods
    QString name() const;
    QStringList files() const;
    bool remove(const QString &file) const;
};

However, I would like to make this wrapper asynchronous, like this:

class AsyncWrapper {
    Q_OBJECT
public:
    // Values are fetched *asynchronously* in these methods
    void name() const;
    void files() const;
    void remove(const QString &file) const;
signals:
    // Values are returned via signals
    void nameReady(const QString &name) const;
    void filesReady(const QStringList &files) const;
    void removeDone(bool success) const;
};

Problems

I'm not sure if this pattern is ok as there are multiple points concerning me:

Possible Solutions

Some other ideas I came up with:

What is the proper way to design such asynchronous wrapper in Qt?


Solution

  • I think the command pattern could work here.

    Start with the abstract command interface:

    class Command : public QObject
    {
        Q_OBJECT
    public:
        virtual ~Command() = default;
        virtual void execute() = 0;
    signals:
        void done(bool success) const;
    };
    

    Subclasses aren't that complicated, just give them some status and override execute, e.g.

    class NameCommand : public Command
    {
        QString _name;
    public:
        void execute() override
        {
            _name = ... //fetch name
            emit done(true);
        }
        QString name() const { return _name; }
    };
    

    or

    class RemoveFileCommand : public Command
    {
        QString _filename;
    public:
        RemoveFileCommand(const QString filename) : _filename(filename){}
        void execute() override
        {
            //remove _filename
    
            emit done(true);
        }
    };
    

    This way you can build a set of objects that perform several different actions, and yet you can implement a command router and keep the chance to run commands asyncrounously or not:

    class CommandRouter : public QObject
    {
        Q_OBJECT
    public:
        void run(Command * command, std::function<void(bool)> done, bool async = false)
        {
            connect(command, &Command::done, this, done, (async ? Qt::QueuedConnection : Qt::DirectConnection));
            if(async)
            {
                QtConcurrent::run(command, &Command::execute);
            }
            else
            {
                command->execute();
            }
        }
    };
    

    So you would end up with something like:

        CommandRouter router;
    
        RemoveFileCommand removecommand("somefile.tar.gz");
        router.run(&removecommand, [](bool success) {
    
            qDebug() << "REMOVE " << (success ? "SUCCESSFUL" : "FAILED");
    
        }, true); //this will run asyncrounously
    
        NameCommand namecommand;
        router.run(&namecommand, [&namecommand](bool success) {
    
            if(success)
            {
                qDebug() << "Name: " + namecommand.name();
            }
            else
            {
                qDebug() << "FETCH NAME FAILED";
            }
    
        }; //this will block