在C ++ / Qt程序中,我需要使用" done"运行一些异步任务。信号(例如网络下载,QProcess
等)按顺序排列,每次都在最后一次完成之后。
我能想到的唯一方法是为每个步骤设置一个单独的状态类(非常详细,比如在同步程序中为每一行设置一个单独的类),或者让一个具有状态枚举和字段的大类保存不同步骤所需的所有可能对象(不灵活,难以维护)。对此有什么好的解决方案吗?这似乎应该是一个常见的问题,但我找不到任何东西。
答案 0 :(得分:4)
QRunnable
是一个可运行的对象。您从中继承您的类并重新实现将执行单个异步作业的QRunnable::run()
方法(例如,下载文件)。
QQueue
是一个简单的容器,它实现了“先进先出”(FIFO)。您可以使用符合您需求的任何其他容器 - QList
,QStack
等
在runnable对象中创建一个done()
信号,并在其run()
方法的末尾发出它。要查询新任务,只需将新的QRunnable
对象推送到容器,然后将done()
信号连接到dequeue
的某个插槽并运行(异步)单个任务。例如,可以使用QtConcurrent::run
来实现异步运行。
您还可以将QRunnable
与QThreadPool
一起使用,并手动设置并发任务的限制。 Here您可以阅读有关Qt多线程技术的更多信息。
答案 1 :(得分:2)
有很多方法可以做到这一点。一个基本模式是将函子连接到done()
信号:
struct Task : QObject {
Q_SLOT void start() {}
Q_SIGNAL void done();
Q_OBJECT
};
int main(int argc, char ** argv) {
QCoreApplication app{argc, argv};
using Q = QObject;
Task task1, task2, task3;
Q::connect(&task1, &Task::done, &task2, [&]{ task2.start(); });
Q::connect(&task2, &Task::done, &task3, [&]{ task3.start(); });
Q::connect(&task3, &Task::done, &app, [&]{ app.quit(); });
return app.exec();
}
我们可以分解关于特定类别的done
信号的知识:
template <typename F> void onDone(QProcess * process, QObject * dst, F && f) {
using signal_type = void(QProcess::*)(int,QProcess::ExitStatus);
QObject::connect(process, static_cast<signal_type>(&QProcess::finished),
dst, std::forward<F>(f));
}
template <typename F> void onDone(QNetworkReply * reply, QObject * dst, F && f) {
QObject::connect(reply, &QNetworkReply::finished, dst, std::forward<F>(f));
}
int main(int argc, char ** argv) {
QCoreApplication app{argc, argv};
QNetworkAccessManager mgr;
auto download = mgr.get(QNetworkRequest{QUrl{"http://www.google.com"}});
QProcess process;
onDone(download, &process, [&]{ process.start(); });
onDone(&process, &app, [&]{ app.quit(); });
return app.exec();
}
如果某个类或一对类中存在特定的特定行为,您也可以将它们排除在外。特征类有助于防止由于多种可能的配对而导致的组合爆炸:
// https://github.com/KubaO/stackoverflown/tree/master/questions/task-sequence-37903585
#include <QtCore>
#include <QtNetwork>
#include <type_traits>
template <typename T> struct SourceAction;
template<> struct SourceAction<QProcess> {
using signal_type = void(QProcess::*)(int,QProcess::ExitStatus);
static constexpr signal_type source(QProcess*) {
return static_cast<signal_type>(&QProcess::finished); }
};
template<> struct SourceAction<QNetworkReply> {
using signal_type = void(QNetworkReply::*)();
static constexpr signal_type source(QNetworkReply*) { return &QNetworkReply::finished; }
};
template <typename T> struct TargetAction;
template<> struct TargetAction<QProcess> {
struct slot_type {
QProcess * process;
void operator()() { process->start(); }
slot_type(QProcess* process) : process(process) {}
};
static slot_type destination(QProcess * process) { return slot_type(process); }
};
template<> struct TargetAction<QCoreApplication> {
using slot_type = void(*)();
static constexpr slot_type destination(QCoreApplication*) { return &QCoreApplication::quit; }
};
// SFINAE
template <typename Src, typename Dst>
void proceed(Src * src, Dst * dst) {
QObject::connect(src, SourceAction<Src>::source(src),
dst, TargetAction<Dst>::destination(dst));
}
template <typename Src, typename F>
void proceed(Src * src, F && f) {
QObject::connect(src, SourceAction<Src>::source(src), std::forward<F>(f));
}
QNetworkReply * download(QNetworkAccessManager * mgr, const QUrl & url) {
return mgr->get(QNetworkRequest{url});
}
QProcess * setup(QProcess * process, const QString & program, const QStringList & args) {
process->setProgram(program);
process->setArguments(args);
return process;
}
int main(int argc, char ** argv) {
QCoreApplication app{argc, argv};
if (app.arguments().count() > 1) return 0;
QNetworkAccessManager mgr;
QProcess process;
proceed(download(&mgr, {"http://www.google.com"}), &process);
proceed(setup(&process, app.applicationFilePath(), {"dummy"}), &app);
proceed(&process, []{ qDebug() << "quitting"; });
return app.exec();
}
你也可以leverage the state machine system以类似的声明方式。