QT异步作用序列

Qt asynchronous action sequence

本文关键字:作用 异步 QT      更新时间:2023-10-16

在C /QT程序中,我需要按照"完成"信号(例如网络下载,QProcess等)运行一些异步任务,在最后一个结束后每个任务。

我唯一能想到的方法是为每个步骤拥有一个单独的状态类(例如,冗长,例如同步程序中的每行单独的类别),或者拥有一个带有状态枚举和字段的大型类保留不同步骤所需的所有可能对象(不灵活,难以维护)。有什么好的解决方案吗?似乎这应该是一个常见的问题,但是我找不到任何东西。

命令模式

我唯一能想到的方法是为每个步骤(极端冗长)拥有一个单独的状态类

实际上,这种方法没有错。它称为命令模式,为每个动作创建一个单独的类。

您可以使用QRunnableQQueue实现它。

  • QRunnable 是一个可运行的对象。您从中继承了类并重新完成run()方法,该方法将执行单个异步工作(例如,下载文件)。
  • QQueue 是一个简单的容器,它实现了"首先,首先出局"(FIFO)。您可以使用任何适合您需求的容器-QListQStack

一般实施

在可运行的对象中创建一个done()信号,并在其run()方法的末尾发射它。要查询新任务,只需将新的QRunnable对象推到容器中,然后将done()信号连接到某个插槽,该插槽将dequeue并运行一个任务。

如果基础类(与QProcessQNetworkManager等不同)不是通过设计异步,则可以使用QtConcurrent::run()来实现异步运行。

另请参见

您也可以将QRunnableQThreadPool一起使用,并手动设置并发任务的限制。在这里,您可以阅读有关QT多线程技术的更多信息。

有很多方法。一种基本模式是将函子连接到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();
}

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