Qt 异步操作序列

5
在一个C++/Qt程序中,我需要按顺序运行几个带有“完成”信号的异步任务(例如网络下载,QProcess等),每一个任务在上一个任务完成后执行。
我能想到的唯一方法是为每个步骤都创建一个单独的状态类(非常冗长,就像在同步程序中为每一行都创建一个单独的类一样),或者创建一个大的类,拥有状态枚举和保存所有可能需要的对象的字段(不灵活,难以维护)。有没有好的解决方案?这似乎应该是一个常见的问题,但我很难找到任何信息。
2个回答

4

命令模式

我所知道的唯一方法是为每个步骤创建一个单独的状态类(非常冗长)。

实际上,这种方法并没有问题。它被称为命令模式,其设计隐含了为每个操作创建一个单独的类。

您可以使用 QRunnableQQueue 来实现它。

  • QRunnable 是一个可运行的对象。您需要从中继承您的类,并重新实现run() 方法,该方法将执行单个异步任务(例如下载文件)。
  • QQueue 是一个简单的容器,它实现了“先进先出”(FIFO)原则。如果需要,您可以使用任何其他适合您需求的容器 – 如 QListQStack 等。

总体实现

在您的可运行对象中创建一个 done() 信号,并在其 run() 方法结束时发射它。要查询新任务,只需将新的 QRunnable 对象推入容器中,并将 done() 信号连接到某个槽上,该槽将出队并运行单个任务。

如果底层类(与 QProcessQNetworkManager 等不同)的设计不是异步的,则可以使用 QtConcurrent::run() 实现异步运行。

另请参阅

您还可以将 QRunnableQThreadPool 一起使用,并手动设置并发任务的限制。 在这里 您可以了解更多关于 Qt 多线程技术的信息。


喜欢它,+1 为提供了一种通用的方式来链接不同的任务:) - code_fodder

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();
}

我们可以将关于特定类的“完成”信号的知识提取出来:
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();
}

您还可以以类似的声明方式利用状态机系统


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接