Qt TaskTree C++ Classes
Enthält eine allgemeine TaskTree-Bibliothek. Mehr...
Dieses Modul befindet sich in der Entwicklung und kann sich noch ändern.
Dieses Modul wurde in Qt 6.11 eingeführt.
Namespaces
Beinhaltet alle Klassen und globalen Funktionen des TaskTree-Moduls |
Klassen
Ein Body-Element, das mit For- und When-Konstrukten verwendet wird | |
Ein "else"-Element, das in bedingten Ausdrücken verwendet wird | |
Ein "else if"-Element, das in bedingten Ausdrücken verwendet wird | |
Basisklasse für ausführbare Aufgabenelemente | |
Gruppenelement zur Beschreibung des Ausführungsmodus | |
Ein for-Schleifen-Element | |
Unendliche Schleife von Unteraufgaben | |
Unendlicher Iterator, der innerhalb des For-Elements verwendet wird | |
Stellt das Basiselement für die Erstellung von deklarativen Rezepten dar, die beschreiben, wie ein verschachtelter Baum von asynchronen Aufgaben auszuführen und zu behandeln ist | |
Stellt das Basiselement dar, das Teil jeder Gruppe sein kann | |
Ein "if"-Element, das in bedingten Ausdrücken verwendet wird | |
Basisklasse, die als Iterator innerhalb eines For-Elements verwendet werden kann | |
Listen-Iterator, der innerhalb des For-Elements verwendet werden kann | |
Struktur zur Beschreibung der QObject-Unterklasse und ihres Signals | |
Paralleler Ausführungsmodus mit einer benutzerdefinierten Begrenzung | |
Eine asynchrone Aufgabe, die bei Bedarf beendet wird | |
Eine Klassenvorlage, die zur Deklaration von benutzerdefinierten Aufgabenelementen und zur Definition ihrer Setup- und Done-Handler verwendet wird | |
Eine Klassenvorlage, die den in QCustomTask verwendeten Standard-Task-Adapter bereitstellt | |
Ein gemappter Taskbaum-Ausführungscontroller mit einem bestimmten Schlüsseltyp | |
Ein Wrapper um QNetworkReply und QNetworkAccessManager | |
Ein Controller für die parallele Ausführung eines Taskbaums | |
Ein benutzerdefinierter Deleter für QProcess, der von QProcessTask verwendet wird | |
Ein Controller für die sequentielle Taskbaumausführung | |
Ein Controller für die Ausführung eines einzelnen Taskbaums | |
Eine gestartete QBarrier mit einem bestimmten Limit | |
Führt einen benutzerdefinierten Handler zwischen anderen Tasks synchron aus | |
Hilfsklasse, die bei der Anpassung der Schnittstelle eines benutzerdefinierten Tasks verwendet wird | |
Führt den deklarativ definierten Baum asynchroner Tasks aus | |
Ein Wrapper um QTcpSocket | |
Eine Klassenvorlage, die die Ausführung einer Funktion in einem separaten Thread über QtConcurrent::run() steuert | |
Eine Basisklasse für QThreadFunction Klassenvorlage | |
Repetitiver Iterator zur Verwendung innerhalb eines For-Elements | |
Eine Klassenvorlage für den benutzerdefinierten Datenaustausch im laufenden Aufgabenbaum | |
Ein "then"-Element, das in bedingten Ausdrücken verwendet wird | |
Bedingter Iterator, der innerhalb des For-Elements verwendet wird | |
Ein Element, das die Ausführung eines Körpers bis zur Barriere aufschiebt |
Detaillierte Beschreibung
Verwenden Sie die TaskTree-Bibliothek, um Rezepte zu erstellen, die beschreiben, welche asynchronen Aufgaben ausgeführt werden sollen, und verwenden Sie diese Rezepte innerhalb von QTaskTree, um sie auszuführen.
Die Rezepte sind deklarative Beschreibungen, welche Aufgabentypen erstellt und ausgeführt werden sollen, z.B: QProcess, QNetworkReplyWrapper, oder QThreadFunction<ReturnType>, oder ob sie nacheinander oder parallel ablaufen sollen. Innerhalb von Rezepten können Sie unterschiedliche Fortsetzungspfade definieren, je nachdem, ob die vorherige Aufgabe mit Erfolg oder einem Fehler beendet wurde. Es ist auch möglich, Aufgaben in Group Elementen zu verschachteln, und jede Group kann ihre Aufgaben gemäß ihrem eigenen Ausführungsmodus oder ihrer eigenen Workflow-Politik ausführen. Die Rezepte bilden die Baumstruktur der Aufgaben.
Asynchrone Aufgaben
Eine asynchrone Aufgabe ist jede Aufgabe, die gestartet werden kann und später mit Erfolg oder einem Fehler beendet wird. Später bedeutet, dass nach dem Start der Aufgabe die Kontrolle an die laufende Ereignisschleife zurückgeht. Wir blockieren den aufrufenden Thread nicht, bis die Aufgabe beendet ist. Um den Aufgabenbaum zu verwenden, benötigen wir eine Ereignisschleife, die sich dreht.
Beispiele für asynchrone Tasks:
- QTimer::singleShot()
- QProcess
- QNetworkAccessManager + QNetworkReply = QNetworkReplyWrapper
- QtConcurrent::run() + QFutureWatcher<Result> = QThreadFunction<Result>
Rezept & Aufgabenbaum
Um sich zu merken, was der Rezept- und Aufgabenbaum ist, lassen Sie uns die Analogie zur Patrone und zum Player verwenden. Wenn wir ein Rezept schreiben, ist es so, als würden wir eine Kassette bauen, also bereiten wir eine detaillierte Beschreibung dessen vor, was der Spieler später tun soll, wenn die Kassette in einen Spieler (Aufgabenbaum) eingesetzt und gestartet wird. Das Rezept selbst ist nur eine deklarative Beschreibung für den Aufgabenbaum, was der Aufgabenbaum tun soll, wenn das Rezept an den Aufgabenbaum übergeben und der Aufgabenbaum gestartet wird. Das Rezept selbst tut nichts ohne einen Aufgabenbaum - es ist wie eine Patrone ohne einen Player.
Hier ist eine kurze Zusammenfassung über die Verantwortlichkeiten von Rezept und Aufgabenbaum.
Rezept (Kassette) beschreibt:
- welche Tasks dynamisch durch den laufenden Task-Tree (über QCustomTask) erzeugt werden sollen
- in welcher Reihenfolge
- welche Datenstrukturen dynamisch durch den laufenden Task-Baum erzeugt werden sollen (über Storage)
- wie man jede Aufgabe vor dem Start einrichtet
- wie die Daten gesammelt werden, wenn die Aufgaben beendet sind
- Ausführungsmodus (Aufgaben sollten nacheinander oder parallel ausgeführt werden)
- Workflow-Richtlinie
Aufgabenbaum (Player):
- liest das Rezept und erstellt automatisch Aufgaben und Datenstrukturen
- verwaltet die Lebensdauer der erstellten Aufgaben und Datenstrukturen
- führt Fortsetzungen aus
- wählt verschiedene Pfade in Abhängigkeit von den Ergebnissen beendeter Aufgaben und Workflow-Richtlinien
- liefert grundlegende Fortschrittsinformationen
Benutzerdefinierte Aufgaben
Da das Rezept eine Beschreibung für den Aufgabenbaum ist, welche Aufgaben er erstellen soll, wenn der Aufgabenbaum gestartet wird, können wir diese Aufgaben nicht direkt in einem Rezept erstellen. Stattdessen benötigen wir eine deklarative Methode, um dem Aufgabenbaum mitzuteilen, dass er diese Aufgaben für uns zu einem späteren Zeitpunkt erstellen und starten soll. Wenn wir zum Beispiel möchten, dass der Aufgabenbaum QProcess erstellt und startet, beschreiben wir dies, indem wir das Element QProcessTask in ein Rezept aufnehmen. Das Element QProcessTask ist ein Alias für QCustomTask<QProcess>. Jeder Aufgabentyp sollte seine entsprechende QCustomTask<Type> zur Verfügung stellen, damit er innerhalb von Rezepten verwendet werden kann.
Die folgende Tabelle zeigt einige eingebaute benutzerdefinierte Aufgaben, die in Rezepte eingefügt werden können:
| Custom Task (in Rezepten verwendet) | Aufgabenklasse (erstellt durch den laufenden Aufgabenbaum) | Kurzbeschreibung |
|---|---|---|
| QProcessTask | QProcess | Startet einen Prozess. |
QThreadFunctionTask<ReturnType> | QThreadFunction<ReturnType> | Startet asynchronen Task, läuft in separatem Thread. |
| QTaskTreeTask | QTaskTree | Startet einen verschachtelten Aufgabenbaum. |
| QNetworkReplyWrapperTask | QNetworkReplyWrapper | Startet einen Netzwerkdownload. |
| QTcpSocketWrapperTask | QTcpSocketWrapper | Startet eine TCP-Verbindung. |
Siehe QTaskInterface und Task Adapters für weitere Informationen darüber, wie man bestimmte Tasks für die Verwendung in Rezepten anpassen kann.
Beispielrezept
QTaskTree hat ein Element der obersten Ebene Group, auch bekannt als Rezept, das eine beliebige Anzahl von Aufgaben verschiedener Typen enthalten kann, z. B. QProcessTask, QNetworkReplyWrapperTask, oder QThreadFunctionTask<ReturnType>:
const Group recipe { QProcessTask(...), QNetworkReplyWrapperTask(...), QThreadFunctionTask<int>(...) }; QTaskTree *taskTree = new QTaskTree(recipe); connect(taskTree, &QTaskTree::done, ...); // finish handler taskTree->start();
Das obige Rezept besteht aus einem Element der obersten Ebene vom Typ Group, das Aufgaben vom Typ QProcessTask, QNetworkReplyWrapperTask, und QThreadFunctionTask<int> enthält. Nach dem Aufruf von taskTree->start() werden die Aufgaben erstellt und in einer Kette ausgeführt, beginnend mit QProcess. Wenn die Aufgabe QProcess erfolgreich abgeschlossen ist, wird die Aufgabe QNetworkReplyWrapper gestartet. Nach erfolgreicher Beendigung der Netzwerkaufgabe wird schließlich die Aufgabe QThreadFunction<int> gestartet.
Wenn die letzte laufende Aufgabe erfolgreich beendet wird, gilt der Aufgabenbaum als erfolgreich ausgeführt und das Signal QTaskTree::done() wird mit DoneWith::Success ausgegeben. Wenn eine Aufgabe mit einem Fehler endet, wird die Ausführung des Aufgabenbaums angehalten und die verbleibenden Aufgaben werden übersprungen. Der Aufgabenbaum beendet sich mit einem Fehler und sendet das Signal QTaskTree::done() mit DoneWith::Error.
Gruppen
Der Elternteil der Group sieht sie als eine einzelne Aufgabe. Wie andere Aufgaben kann die Gruppe gestartet werden und sie kann mit Erfolg oder einem Fehler beendet werden. Die Elemente von Group können verschachtelt werden, um eine Baumstruktur zu erstellen:
const Group recipe { Group { parallel, QProcessTask(...), QThreadFunctionTask<int>(...) }, QNetworkReplyWrapperTask(...) };
Das obige Beispiel unterscheidet sich vom ersten Beispiel dadurch, dass das Element der obersten Ebene eine Untergruppe hat, die die Elemente QProcessTask und QThreadFunctionTask<int> enthält. Die Untergruppe ist ein geschwisterliches Element des QNetworkReplyWrapperTask im Stamm. Die Untergruppe enthält ein zusätzliches Element parallel, das die Group anweist, ihre Aufgaben parallel auszuführen.
Wenn also QTaskTree das obige Rezept startet, beginnen die Elemente QProcess und QThreadFunction<int> sofort und laufen parallel. Da die Stammgruppe kein parallel -Element enthält, werden ihre direkten untergeordneten Aufgaben nacheinander ausgeführt. So beginnt die QNetworkReplyWrapper, wenn die gesamte Untergruppe beendet ist. Die Gruppe gilt als abgeschlossen, wenn alle ihre Aufgaben beendet sind. Die Reihenfolge, in der die Aufgaben beendet werden, ist nicht relevant.
Je nachdem, welche Aufgabe länger dauert (QProcess oder QThreadFunction<int>), können also folgende Szenarien eintreten:
| Szenario 1 | Szenario 2 |
|---|---|
| Wurzelgruppe beginnt | Wurzelgruppe startet |
| Untergruppe startet | Untergruppe startet |
| QProcess startet | QProcess startet |
| QThreadFunction<int> startet | QThreadFunction<int> startet |
| ... | ... |
| QProcess beendet | QThreadFunction<int> beendet |
| ... | ... |
| QThreadFunction<int> beendet | QProcess beendet |
| Untergruppe beendet | Untergruppe beendet |
| QNetworkReplyWrapper beginnt | QNetworkReplyWrapper startet |
| ... | ... |
| QNetworkReplyWrapper beendet | QNetworkReplyWrapper beendet |
| Root Group beendet | Root Group beendet |
Die Unterschiede zwischen den Szenarien sind durch Fettdruck gekennzeichnet. Drei Punkte bedeuten, dass zwischen dem vorherigen und dem nächsten Ereignis eine unbestimmte Zeit vergeht (eine Aufgabe oder mehrere Aufgaben laufen weiter). Keine Punkte zwischen den Ereignissen bedeuten, dass sie synchron ablaufen.
Bei den dargestellten Szenarien wird davon ausgegangen, dass alle Aufgaben erfolgreich ausgeführt werden. Wenn eine Aufgabe während der Ausführung fehlschlägt, wird der Aufgabenbaum mit einem Fehler beendet. Wenn insbesondere QProcess mit einem Fehler endet, während QThreadFunction<int> noch ausgeführt wird, wird QThreadFunction<int> automatisch abgebrochen, die Untergruppe endet mit einem Fehler, QNetworkReplyWrapper wird übersprungen und der Baum endet mit einem Fehler.
Aufgaben-Handler
Verwenden Sie Task Handler, um eine Aufgabe für die Ausführung einzurichten und um das Lesen der Ausgabedaten aus der Aufgabe zu ermöglichen, wenn diese erfolgreich oder mit einem Fehler beendet wurde.
Start-Handler der Aufgabe
Wenn ein Task-Objekt erstellt wird und bevor es gestartet wird, ruft der Task-Baum einen optionalen, vom Benutzer bereitgestellten Setup-Handler auf. Der Setup-Handler sollte immer einen Verweis auf das zugehörige Task-Klassenobjekt enthalten:
const auto onSetup = [](QProcess &process) { process.setProgram("sleep"); process.setArguments({"3"}); }; const Group root { QProcessTask(onSetup) };
Sie können die übergebene QProcess im Setup-Handler ändern, so dass der Aufgabenbaum den Prozess gemäß Ihrer Konfiguration starten kann. Sie sollten process.start(); nicht im Setup-Handler aufrufen, da der Aufgabenbaum ihn bei Bedarf aufruft. Der Setup-Handler ist optional. Wenn er verwendet wird, muss er das erste Argument im Konstruktor der Aufgabe sein.
Optional kann der Setup-Handler eine SetupResult zurückgeben. Die zurückgegebene SetupResult beeinflusst das weitere Startverhalten einer bestimmten Aufgabe. Die möglichen Werte sind:
| SetupResult Wert | Kurzbeschreibung |
|---|---|
| Continue | Die Aufgabe wird normal gestartet. Dies ist das Standardverhalten, wenn der Setup-Handler nicht SetupResult zurückgibt (d.h. sein Rückgabetyp ist void). |
| StopWithSuccess | Die Aufgabe wird nicht gestartet und meldet den Erfolg an ihre Muttergesellschaft. |
| StopWithError | Die Aufgabe wird nicht gestartet und meldet einen Fehler an ihre Muttergesellschaft. |
Dies ist nützlich, um eine Aufgabe nur dann auszuführen, wenn eine Bedingung erfüllt ist und die zur Bewertung dieser Bedingung erforderlichen Daten erst nach Abschluss der zuvor gestarteten Aufgaben bekannt sind. Auf diese Weise entscheidet der Setup-Handler dynamisch, ob die entsprechende Aufgabe normal gestartet oder übersprungen und ein Erfolg oder ein Fehler gemeldet werden soll. Weitere Informationen über den Datenaustausch zwischen Aufgaben finden Sie unter Storage.
Erledigt-Handler der Aufgabe
Wenn eine laufende Aufgabe beendet wird, ruft der Aufgabenbaum einen optionalen Done-Handler auf. Der Handler sollte einen const Verweis auf das zugehörige Task-Klassenobjekt enthalten:
const auto onSetup = [](QProcess &process) { process.setProgram("sleep"); process.setArguments({"3"}); };const auto onDone = [](const QProcess &process, DoneWith result) { if (result == DoneWith::Success) qDebug() << "Success" << process.cleanedStdOut(); sonst qDebug() << "Failure" << process.cleanedStdErr(); };const Group root { QProcessTask(onSetup, onDone) };
Der done-Handler kann Ausgabedaten von QProcess sammeln und für die weitere Verarbeitung speichern oder zusätzliche Aktionen durchführen.
Hinweis: Wenn der Task-Setup-Handler StopWithSuccess oder StopWithError zurückgibt, wird der Done-Handler nicht aufgerufen.
Gruppen-Handler
Ähnlich wie bei den Aufgaben-Handlern können Sie mit den Gruppen-Handlern eine Gruppe einrichten, die ausgeführt werden soll, und weitere Aktionen ausführen, wenn die gesamte Gruppe erfolgreich oder mit einem Fehler abgeschlossen wurde.
Start-Handler der Gruppe
Der Aufgabenbaum ruft den Gruppenstart-Handler auf, bevor er die untergeordneten Aufgaben startet. Der Gruppenhandler nimmt keine Argumente entgegen:
const auto onSetup = [] { qDebug() << "Entering the group"; };const Group root { onGroupSetup(onSetup), QProcessTask(...) };
Der Gruppen-Setup-Handler ist optional. Um einen Gruppen-Setup-Handler zu definieren, fügen Sie ein onGroupSetup()-Element zu einer Gruppe hinzu. Das Argument von onGroupSetup() ist ein Benutzer-Handler. Wenn Sie mehr als ein onGroupSetup()-Element zu einer Gruppe hinzufügen, wird zur Laufzeit ein Assert ausgelöst, das eine Fehlermeldung enthält.
Wie der Starthandler der Aufgabe kann auch der Gruppenstarthandler SetupResult zurückgeben. Der zurückgegebene Wert SetupResult wirkt sich auf das Startverhalten der gesamten Gruppe aus. Wenn Sie keinen Gruppenstart-Handler angeben oder sein Rückgabetyp ungültig ist, ist die Standardaktion der Gruppe Continue, so dass alle Aufgaben normal gestartet werden. Andernfalls, wenn der Start-Handler den Wert StopWithSuccess oder StopWithError zurückgibt, werden die Aufgaben nicht gestartet (sie werden übersprungen) und die Gruppe selbst meldet einen Erfolg oder einen Fehler, je nach dem zurückgegebenen Wert.
const Group root { onGroupSetup([] { qDebug() << "Root setup"; }), Group { onGroupSetup([] { qDebug() << "Group 1 setup"; return SetupResult::Continue; }), QProcessTask(...) // Process 1 }, Group { onGroupSetup([] { qDebug() << "Group 2 setup"; return SetupResult::StopWithSuccess; }), QProcessTask(...) // Process 2 }, Group { onGroupSetup([] { qDebug() << "Group 3 setup"; return SetupResult::StopWithError; }), QProcessTask(...) // Process 3 }, QProcessTask(...) // Process 4 };
Im obigen Beispiel definieren alle Untergruppen einer Stammgruppe ihre Starthandler. Im folgenden Szenario wird davon ausgegangen, dass alle gestarteten Prozesse erfolgreich abgeschlossen werden:
| Szenario | Kommentar |
|---|---|
| Wurzelgruppe startet | Gibt kein SetupResult zurück, also werden ihre Aufgaben ausgeführt. |
| Gruppe 1 startet | Gibt Continue zurück, so dass ihre Aufgaben ausgeführt werden. |
| Prozess 1 startet | |
| ... | ... |
| Prozess 1 beendet (Erfolg) | |
| Gruppe 1 beendet (Erfolg) | |
| Gruppe 2 startet | Gibt StopWithSuccess zurück, also wird Prozess 2 übersprungen und Gruppe 2 meldet Erfolg. |
| Gruppe 2 beendet (Erfolg) | |
| Gruppe 3 beginnt | Gibt StopWithError zurück, so dass Prozess 3 übersprungen wird und Gruppe 3 einen Fehler meldet. |
| Gruppe 3 wird beendet (Fehler) | |
| Wurzelgruppe beendet (Fehler) | Gruppe 3, die ein direktes Kind der Stammgruppe ist, wurde mit einem Fehler beendet, so dass die Stammgruppe die Ausführung stoppt, Prozess 4 überspringt, der noch nicht begonnen hat, und einen Fehler meldet. |
Done Handler von Gruppen
Der Done-Handler von Group wird nach der erfolgreichen oder fehlgeschlagenen Ausführung seiner Aufgaben ausgeführt. Der endgültige Wert, der von der Gruppe gemeldet wird, hängt von ihrer Workflow Policy ab. Der Handler kann weitere notwendige Aktionen ausführen. Der done-Handler wird innerhalb des onGroupDone()-Elements einer Gruppe definiert. Er kann das optionale Argument DoneWith annehmen, das die erfolgreiche oder fehlgeschlagene Ausführung angibt:
const Group root { onGroupSetup([] { qDebug()<< "Root setup"; }), QProcessTask(...),onGroupDone([](DoneWith result) { if (result == DoneWith::Success) qDebug() << "Root finished with success"; sonst qDebug() << "Root finished with an error"; }) };
Der Group-Done-Handler ist optional. Wenn Sie mehr als eine onGroupDone() zu einer Gruppe hinzufügen, wird zur Laufzeit ein Assert ausgelöst, das eine Fehlermeldung enthält.
Hinweis: Auch wenn der Group Setup Handler StopWithSuccess oder StopWithError zurückgibt, wird der Done Handler der Gruppe aufgerufen. Dieses Verhalten unterscheidet sich von dem des Task-Done-Handlers und könnte sich in Zukunft ändern.
Andere Gruppenelemente
Eine Gruppe kann andere Elemente enthalten, die den Verarbeitungsablauf beschreiben, z. B. execution mode oder workflow policy. Sie kann auch Speicherelemente enthalten, die für das Sammeln und die gemeinsame Nutzung von benutzerdefinierten gemeinsamen Daten zuständig sind, die während der Gruppenausführung gesammelt werden.
Ausführungsmodus
Das Element Ausführungsmodus in einer Gruppe gibt an, wie die direkten untergeordneten Aufgaben der Gruppe gestartet werden. Die gebräuchlichsten Ausführungsmodi sind sequential und parallel. Es ist auch möglich, die Anzahl der parallel ausgeführten Aufgaben mit dem Element ParallelLimit festzulegen.
In allen Ausführungsmodi startet eine Gruppe die Aufgaben in der Reihenfolge, in der sie erscheinen.
Wenn ein Kind einer Gruppe auch eine Gruppe ist, führt die Kindgruppe ihre Aufgaben gemäß ihrem eigenen Ausführungsmodus aus.
Workflow-Richtlinie
Das Element Workflow-Policy in einer Group gibt an, wie sich die Gruppe verhalten soll, wenn eine ihrer direkten untergeordneten Aufgaben beendet wird. Eine detaillierte Beschreibung der möglichen Richtlinien finden Sie unter WorkflowPolicy.
Wenn ein Kind einer Gruppe auch eine Gruppe ist, führt die Kindgruppe ihre Aufgaben gemäß ihrer eigenen Workflow-Richtlinie aus.
Speicherung
Verwenden Sie das Element Storage, um Informationen zwischen Aufgaben auszutauschen. Insbesondere im sequenziellen Ausführungsmodus, wenn eine Aufgabe Daten von einer anderen, bereits abgeschlossenen Aufgabe benötigt, bevor sie beginnen kann. Ein Aufgabenbaum, der Daten kopiert, indem er sie von einer Quelle liest und in ein Ziel schreibt, könnte zum Beispiel wie folgt aussehen:
static QByteArray load(const QString &Dateiname) { ... }static void save(const QString &Dateiname, const QByteArray &array) { ... }static Group copyRecipe(const QString &Quelle, const QString &destination) { struct CopyStorage { // [1] benutzerdefinierte aufgabenübergreifende Struktur QByteArray content; // [2] benutzerdefinierte aufgabenübergreifende Daten}; // [3] Instanz einer benutzerdefinierten aufgabenübergreifenden Struktur, die vom Aufgabenbaum verwaltet werden kann const Storage<CopyStorage> storage; const auto onLoaderSetup = [source](QThreadFunction<QByteArray> &async) { async.setThreadFunctionData(&load, source); }; // [4] Laufzeit: Aufgabenbaum aktiviert die Instanz aus [7] vor Aufruf des Handlers const auto onLoaderDone = [storage](const QThreadFunction<QByteArray> &async) { storage->content = async.result(); // [5] loader speichert das Ergebnis in storage}; // [4] Laufzeit: Aufgabenbaum aktiviert die Instanz aus [7] vor dem Aufruf des Handlers const auto onSaverSetup = [storage, destination](const QThreadFunction<void> &async) { const QByteArray content = storage->content; // [6] saver entnimmt Daten aus storageasync.setThreadFunctionData(&save, destination, content); }; const auto onSaverDone = [](const QThreadFunction<void> &async) { qDebug() << "Save done successfully"; }; const Group root { // [7] Laufzeit: Aufgabenbaum erzeugt eine Instanz von CopyStorage, wenn root eingegeben wirdstorage, QThreadFunctionTask<QByteArray>(onLoaderSetup, onLoaderDone, CallDoneFlag::OnSuccess),QThreadFunctionTask<void>(onSaverSetup, onSaverDone, CallDoneFlag::OnSuccess) }; return root; }...const QString Quelle = ...;const QString destination = ...;QTaskTree taskTree(copyRecipe(source, destination)); connect(&taskTree, &QTaskTree::done, &taskTree, [](DoneWith result) { if (result == DoneWith::Success) qDebug() << "The copying finished successfully."; }); tasktree.start();
Im obigen Beispiel bestehen die Inter-Task-Daten aus einer QByteArray Inhaltsvariablen [2], die in einer CopyStorage benutzerdefinierten Struktur [1] eingeschlossen ist. Wenn der Lader erfolgreich abschließt, speichert er die Daten in einer CopyStorage::content -Variablen [5]. Der Saver verwendet dann die Variable zur Konfiguration der Speicheraufgabe [6].
Damit ein Taskbaum die CopyStorage struct verwalten kann, wird eine Instanz von Storage<CopyStorage> erstellt [3]. Wenn eine Kopie dieses Objekts als untergeordnetes Element der Gruppe eingefügt wird [7], wird eine Instanz der CopyStorage struct dynamisch erstellt, wenn der Aufgabenbaum diese Gruppe betritt. Wenn der Aufgabenbaum diese Gruppe verlässt, wird die bestehende Instanz der CopyStorage struct zerstört, da sie nicht mehr benötigt wird.
Wenn mehrere Task-Bäume, die eine Kopie der gemeinsamen Instanz Storage<CopyStorage> enthalten, gleichzeitig laufen (einschließlich des Falles, dass die Task-Bäume in verschiedenen Threads ausgeführt werden), enthält jeder Task-Baum seine eigene Kopie der CopyStorage struct.
Sie können auf CopyStorage von jedem Handler in der Gruppe mit einem Speicherobjekt zugreifen. Dies schließt alle Handler aller abhängigen Aufgaben der Gruppe mit einem Speicherobjekt ein. Um auf die benutzerdefinierte Struktur in einem Handler zuzugreifen, übergeben Sie die Kopie des Storage<CopyStorage> Objekts an den Handler (zum Beispiel in einem Lambda-Capture) [4].
Wenn der Aufgabenbaum einen Handler in einem Teilbaum aufruft, der den Speicher enthält [7], aktiviert der Aufgabenbaum seine eigene CopyStorage -Instanz innerhalb des Storage<CopyStorage>-Objekts. Daher kann auf die CopyStorage struct nur innerhalb des Handler-Bodys zugegriffen werden. Um von Storage<CopyStorage> aus auf die derzeit aktive CopyStorage zuzugreifen, verwenden Sie die Methode Storage::operator->(), Storage::operator*() oder Storage::activeStorage().
Die folgende Liste fasst zusammen, wie man ein Speicherobjekt in den Aufgabenbaum einfügt:
- Definieren Sie die benutzerdefinierte Struktur
MyStoragemit benutzerdefinierten Daten [1], [2] - Erstellen Sie eine Instanz des Storage<
MyStorage> Speichers [3] - Übergeben Sie die Instanz Storage<
MyStorage> an Handler [4] - Zugriff auf die Instanz
MyStoragein Handlern [5], [6] - Einfügen der Instanz Storage<
MyStorage> in eine Gruppe [7]
QTaskTree-Klasse
QTaskTree führt die Baumstruktur der asynchronen Tasks nach dem durch das Group Wurzelelement beschriebenen Rezept aus.
Da QTaskTree ebenfalls eine asynchrone Aufgabe ist, kann sie ein Teil einer anderen QTaskTree sein. Um eine verschachtelte QTaskTree innerhalb einer anderen QTaskTree zu platzieren, fügen Sie das QTaskTreeTask Element in ein anderes Group Element ein.
QTaskTree meldet den Fortschritt abgeschlossener Aufgaben während der Ausführung. Der Fortschrittswert wird erhöht, wenn eine Aufgabe beendet, übersprungen oder abgebrochen wird. Wenn QTaskTree beendet ist und das Signal QTaskTree::done() ausgegeben wird, ist der aktuelle Wert des Fortschritts gleich dem maximalen Fortschrittswert. Der maximale Fortschritt ist gleich der Gesamtzahl der asynchronen Aufgaben in einem Baum. Eine verschachtelte QTaskTree wird als einzelne Aufgabe gezählt, und ihre untergeordneten Aufgaben werden im Baum der obersten Ebene nicht gezählt. Gruppen selbst werden nicht als Aufgaben gezählt, aber ihre Aufgaben werden gezählt. QSyncTask Aufgaben sind nicht asynchron und werden daher nicht als Aufgaben gezählt.
Um zusätzliche Anfangsdaten für den laufenden Baum festzulegen, ändern Sie die Speicherinstanzen in einem Baum, wenn er sie erstellt, indem Sie einen Storage Setup Handler installieren:
Storage<CopyStorage> storage; const Group root = ...; // storage placed inside root's group and inside handlers QTaskTree taskTree(root); auto initStorage = [](CopyStorage &storage) { storage.content = "initial content"; }; taskTree.onStorageSetup(storage, initStorage); taskTree.start();
Wenn der laufende Task-Baum eine CopyStorage Instanz erzeugt und bevor ein Handler innerhalb des Baums aufgerufen wird, ruft der Task-Baum den initStorage Handler auf, um die Einrichtung der Anfangsdaten des Speichers zu ermöglichen, die nur für diesen speziellen Lauf von taskTree gelten.
In ähnlicher Weise können Sie zusätzliche Ergebnisdaten aus dem laufenden Baum sammeln, indem Sie sie aus den Speicherinstanzen im Baum lesen, wenn diese kurz vor der Zerstörung stehen. Um dies zu tun, installieren Sie einen Storage-Done-Handler:
Storage<CopyStorage> storage;const Group root = ...; // storage platziert innerhalb der Gruppe root und innerhalb des HandlersQTaskTree taskTree(root);auto collectStorage = [](const CopyStorage &storage) { qDebug() << "final content" << storage.content; }; taskTree.onStorageDone(storage, collectStorage); taskTree.start();
Wenn der laufende TaskTree im Begriff ist, eine Instanz von CopyStorage zu zerstören, ruft der TaskTree den CollectStorage-Handler auf, um das Lesen der endgültigen Daten aus dem Speicher zu ermöglichen, die nur für diesen speziellen Lauf von taskTree gelten.
Task-Adapter
Es ist recht einfach, neue Task-Typen als Teil von Rezepten zuzulassen. Es reicht aus, einen neuen Task-Alias für die Vorlage QCustomTask zu definieren und den Typ Task als erstes Argument der Vorlage zu übergeben, etwa so:
class Worker : public QObject { public: void start() { ... } signals: void done(bool result); }; using WorkerTask = QCustomTask<Worker>;
Dies wird funktionieren, wenn die folgenden Bedingungen erfüllt sind:
- Ihre Aufgabe ist von QObject abgeleitet.
- Ihre Aufgabe hat eine öffentliche start()-Methode, die die Aufgabe startet.
- Ihre Aufgabe gibt das Signal done(bool) oder done(DoneResult) aus, wenn sie beendet ist.
Wenn Ihre Aufgabe diese Bedingungen nicht erfüllt, können Sie trotzdem Ihre Aufgabe anpassen um mit dem TaskTree Framework zu arbeiten, indem Sie ein zweites Template Argument mit dem benutzerdefinierten Adapter bereitstellen. Nehmen wir an, wir wollen QTimer für die Arbeit mit TaskTree anpassen. Die Adapter könnte wie folgt aussehen:
class TimerAdapter { public: void operator()(QTimer *task, QTaskInterface *iface) { task->setSingleShot(true); QObject::connect(task, &QTimer::timeout, iface, [iface] { iface->reportDone(DoneResult::Success); }); task->start(); } }; using TimerTask = QCustomTask<QTimer, TimerAdapter>;
Jetzt können Sie TimerTask in Ihren Rezepten verwenden, wie z.B.:
const auto onSetup = [](QTimer &task) { task.setInterval(2000); };const auto onDone = [](const QTimer &task) { qDebug() << "Timer triggered after" << task.interval() << "ms."; };const Group recipe { TimerTask(onSetup, onDone) };
Hinweis: Die Klasse, die die laufende Aufgabe implementiert, sollte einen Standardkonstruktor haben, und Objekte dieser Klasse sollten frei zerstörbar sein. Es sollte erlaubt sein, eine laufende Aufgabe zu zerstören, vorzugsweise ohne auf die Beendigung der laufenden Aufgabe zu warten (d. h. ein sicherer, nicht blockierender Destruktor einer laufenden Aufgabe). Um eine nicht-blockierende Zerstörung einer Aufgabe zu erreichen, die einen blockierenden Destruktor hat, sollten Sie den optionalen Deleter Template-Parameter der QCustomTask (das dritte Template-Argument) verwenden.
Taskbaum-Läufer
Der Taskbaum-Läufer verwaltet die Lebensdauer der zugrundeliegenden QTaskTree, die zur Ausführung des angegebenen Rezepts verwendet wird.
Die folgende Tabelle fasst die Unterschiede zwischen den verschiedenen Task-Tree-Runnern zusammen:
| Name der Klasse | Beschreibung |
|---|---|
| QSingleTaskTreeRunner | Verwaltet die Ausführung eines einzelnen Aufgabenbaums. Die Methode QSingleTaskTreeRunner::start() startet das übergebene Rezept bedingungslos und setzt einen eventuell laufenden Aufgabenbaum zurück. Es kann jeweils nur ein Aufgabenbaum ausgeführt werden. |
| QSequentialTaskTreeRunner | Verwaltet sequentielle Aufgabenbaumausführungen. Die Methode QSequentialTaskTreeRunner::enqueue() startet das übergebene Rezept, wenn der Task-Tree-Läufer im Leerlauf ist. Andernfalls wird das Rezept in die Warteschlange gestellt. Wenn die aktuelle Aufgabe beendet ist, führt der Läufer das Rezept aus der Warteschlange sequentiell aus. Es kann immer nur ein Aufgabenbaum gleichzeitig ausgeführt werden. |
| QParallelTaskTreeRunner | Verwaltet parallele Aufgabenbaumausführungen. Die Methode QParallelTaskTreeRunner::start() startet das übergebene Rezept bedingungslos und hält alle möglicherweise laufenden Aufgabenbäume parallel. |
| QMappedTaskTreeRunner | Verwaltet gemappte Aufgabenbaumausführungen. Die Methode QMappedTaskTreeRunner::start() startet bedingungslos das angegebene Rezept für einen bestimmten Schlüssel. Wenn Sie bereits einen anderen Aufgabenbaum mit demselben Schlüssel laufen haben, wird dieser zurückgesetzt. Aufgabenbäume mit anderen Schlüsseln sind davon nicht betroffen und setzen ihre Ausführung fort. |
© 2026 The Qt Company Ltd. Documentation contributions included herein are the copyrights of their respective owners. The documentation provided herein is licensed under the terms of the GNU Free Documentation License version 1.3 as published by the Free Software Foundation. Qt and respective logos are trademarks of The Qt Company Ltd. in Finland and/or other countries worldwide. All other trademarks are property of their respective owners.