How to design asynchronous wrapper returning values in Qt?

434 views Asked by At

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

  • are time-consuming.
  • need to return the values of different types.

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:

  • Duplication. Imagine that there are 100 methods. Each of these methods will need a dedicated signal.
  • Race condition. If I run the same method multiple times, I cannot know the order of the signals I catch.

Possible Solutions

Some other ideas I came up with:

  • Stick to synchronous wrapper and use QtConcurrent::run when calling the class methods. Unfortunately, this is a very bulky solution.
  • Return QFuture objects from methods. Again, this will require handling them using QFutureWatchers outside the class which is still very bulky.
  • Create a separate class for each method. If I'm not mistaken, this is a command pattern. Though this will majorly increase the amount of classes, I believe this is the cleanest solution.

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

1

There are 1 answers

0
p-a-o-l-o On BEST ANSWER

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