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;
};
I'm not sure if this pattern is ok as there are multiple points concerning me:
Some other ideas I came up with:
QtConcurrent::run
when calling the class methods. Unfortunately, this is a very bulky solution.QFuture
objects from methods. Again, this will require handling them using QFutureWatcher
s outside the class which is still very bulky.What is the proper way to design such asynchronous wrapper in Qt?
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