Qt Asynchronous Action Sequence

In a C ++ / Qt program, I need to perform several asynchronous tasks with "made" signals (for example, network downloads, QProcess , etc.) in a sequence, each after the last completion.

The only ways I can think of is to have a separate class of states for each step (extremely detailed, for example, a separate class for each line in a synchronous program) or have one large class with an enumeration of state and fields for storing all possible objects needed for different stages (inflexible, difficult to maintain). Are there any good solutions for this? It seems like this should be a common problem, but it's hard for me to find anything.

+5
source share
2 answers

There are many ways to do this. One basic pattern is to connect functors to done() signals:

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

We can share knowledge about the done signal of a particular class:

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

If there are special behaviors that are common to the class, or a couple of them, you can also decompose them. Character classes help prevent a combinatorial explosion due to the many possible mating:

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

You can also use a state machine system in the same declarative way.

+2
source

How about using QRunnable and QQueue ?

QRunnable is an executable object. You inherit your class from it and override the QRunnable::run() method, which will perform one asynchronous task (for example, upload a file).

QQueue is a simple container that implements "first entry, first exit" ( FIFO ). You can use any other container that suits your needs - QList , QStack , etc.

General implementation:

Create a done() signal in your running object and emit it at the end of your run() method. To request a new task, simply click on your new QRunnable object on the container and connect the done() signal to some slot, which will be dequeue and execute (asynchronously) one task. For example, asynchronous startup can be performed using QtConcurrent::run .


You can also use QRunnable with QThreadPool and manually set the limit for concurrent tasks. Here you can learn more about Qt's multi-threaded technologies.

+4
source

All Articles