如何在 Qt 中设计异步包装器返回值?
How to design asynchronous wrapper returning values in Qt?
我在 Qt 中为外部可执行文件编写了一个包装器 class。
它有很多方法,其中大部分是:
- 很费时间。
- 需要return不同类型的值。
在这种情况下,同步 包装器非常简单:
class SyncWrapper {
public:
// Values are fetched *synchronously* in these methods
QString name() const;
QStringList files() const;
bool remove(const QString &file) const;
};
但是,我想使这个包装器异步,像这样:
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;
};
问题
我不确定这个模式是否正确,因为有很多点与我有关:
- 重复。 假设有 100 个方法。这些方法中的每一种都需要专用信号。
- 竞争条件。如果我多次运行相同的方法,我无法知道我捕获的信号的顺序。
可能的解决方案
我想到的其他一些想法:
- 坚持使用同步包装器并在调用 class 方法时使用
QtConcurrent::run
。不幸的是,这是一个非常庞大的解决方案。
- Return
QFuture
来自方法的对象。同样,这将需要在 class 之外使用 QFutureWatcher
s 来处理它们,这仍然非常笨重。
- 为每个方法创建一个单独的 class。如果我没记错的话,这是一个 命令模式 。虽然这会大大增加 classes 的数量,但我相信这是最干净的解决方案。
在 Qt 中设计这种异步包装器的正确方法是什么?
我认为命令模式可以在这里工作。
从抽象命令接口开始:
class Command : public QObject
{
Q_OBJECT
public:
virtual ~Command() = default;
virtual void execute() = 0;
signals:
void done(bool success) const;
};
子类并没有那么复杂,只需给它们一些状态并覆盖 execute
,例如
class NameCommand : public Command
{
QString _name;
public:
void execute() override
{
_name = ... //fetch name
emit done(true);
}
QString name() const { return _name; }
};
或
class RemoveFileCommand : public Command
{
QString _filename;
public:
RemoveFileCommand(const QString filename) : _filename(filename){}
void execute() override
{
//remove _filename
emit done(true);
}
};
通过这种方式,您可以构建一组执行多个不同操作的对象,但您可以实现一个命令路由器,并保持 运行 异步或非异步命令的机会:
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();
}
}
};
所以你最终会得到这样的结果:
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
我在 Qt 中为外部可执行文件编写了一个包装器 class。 它有很多方法,其中大部分是:
- 很费时间。
- 需要return不同类型的值。
在这种情况下,同步 包装器非常简单:
class SyncWrapper {
public:
// Values are fetched *synchronously* in these methods
QString name() const;
QStringList files() const;
bool remove(const QString &file) const;
};
但是,我想使这个包装器异步,像这样:
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;
};
问题
我不确定这个模式是否正确,因为有很多点与我有关:
- 重复。 假设有 100 个方法。这些方法中的每一种都需要专用信号。
- 竞争条件。如果我多次运行相同的方法,我无法知道我捕获的信号的顺序。
可能的解决方案
我想到的其他一些想法:
- 坚持使用同步包装器并在调用 class 方法时使用
QtConcurrent::run
。不幸的是,这是一个非常庞大的解决方案。 - Return
QFuture
来自方法的对象。同样,这将需要在 class 之外使用QFutureWatcher
s 来处理它们,这仍然非常笨重。 - 为每个方法创建一个单独的 class。如果我没记错的话,这是一个 命令模式 。虽然这会大大增加 classes 的数量,但我相信这是最干净的解决方案。
在 Qt 中设计这种异步包装器的正确方法是什么?
我认为命令模式可以在这里工作。
从抽象命令接口开始:
class Command : public QObject
{
Q_OBJECT
public:
virtual ~Command() = default;
virtual void execute() = 0;
signals:
void done(bool success) const;
};
子类并没有那么复杂,只需给它们一些状态并覆盖 execute
,例如
class NameCommand : public Command
{
QString _name;
public:
void execute() override
{
_name = ... //fetch name
emit done(true);
}
QString name() const { return _name; }
};
或
class RemoveFileCommand : public Command
{
QString _filename;
public:
RemoveFileCommand(const QString filename) : _filename(filename){}
void execute() override
{
//remove _filename
emit done(true);
}
};
通过这种方式,您可以构建一组执行多个不同操作的对象,但您可以实现一个命令路由器,并保持 运行 异步或非异步命令的机会:
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();
}
}
};
所以你最终会得到这样的结果:
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