Auf dieser Seite

Qt TaskTree C++ Classes

Enthält eine universell einsetzbare TaskTree-Bibliothek. Mehr...

Dieses Modul befindet sich in der Technology Preview und kann sich noch ändern.

Dieses Modul wurde in Qt 6.11 eingeführt.

Namensräume

QtTaskTree

Umfasst alle Klassen und globalen Funktionen des TaskTree-Moduls

Klassen

QtTaskTree::Do

Ein Body-Element, das in „For“- und „When“-Konstrukten verwendet wird

QtTaskTree::Else

Ein „else“-Element, das in bedingten Ausdrücken verwendet wird

QtTaskTree::ElseIf

Ein „else if“-Element, das in bedingten Ausdrücken verwendet wird

QtTaskTree::ExecutableItem

Basisklasse für ausführbare Aufgabenelemente

QtTaskTree::ExecutionMode

Gruppenelement zur Beschreibung des Ausführungsmodus

QtTaskTree::For

Ein „for“-Schleifenelement

QtTaskTree::Forever

Unendliche Schleife von Unteraufgaben

QtTaskTree::ForeverIterator

Unendlicher Iterator zur Verwendung innerhalb eines „For“-Elements

QtTaskTree::Group

Stellt das Grundelement für die Erstellung deklarativer Rezepte dar, die beschreiben, wie ein verschachtelter Baum asynchroner Aufgaben ausgeführt und verwaltet wird

QtTaskTree::GroupItem

Stellt das Grundelement dar, das Teil einer beliebigen Gruppe sein kann

QtTaskTree::If

Ein „if“-Element, das in bedingten Ausdrücken verwendet wird

QtTaskTree::Iterator

Basisklasse, die als Iterator innerhalb eines „For“-Elements verwendet wird

QtTaskTree::ListIterator

Listen-Iterator zur Verwendung innerhalb des „For“-Elements

QtTaskTree::ObjectSignal

Struktur, die eine QObject-Unterklasse und deren Signal beschreibt

QtTaskTree::ParallelLimit

Paralleler Ausführungsmodus mit einer benutzerdefinierten Begrenzung

QtTaskTree::QBarrier

Eine asynchrone Aufgabe, die bei Bedarf beendet wird

QtTaskTree::QCustomTask

Eine Klassenvorlage zur Deklaration benutzerdefinierter Aufgabenelemente und zur Definition ihrer Setup- und Done-Handler

QtTaskTree::QDefaultTaskAdapter

Eine Klassenvorlage, die einen Standard-Aufgabenadapter bereitstellt, der in `QCustomTask` verwendet wird

QtTaskTree::QMappedTaskTreeRunner

Ein Ausführungscontroller für einen abgebildeten Aufgabenbaum mit einem vorgegebenen Schlüsseltyp

QtTaskTree::QNetworkReplyWrapper

Ein Wrapper um QNetworkReply und QNetworkAccessManager

QtTaskTree::QParallelTaskTreeRunner

Ein paralleler Ausführungscontroller für Aufgabenbäume

QtTaskTree::QProcessTaskDeleter

Ein benutzerdefinierter Löscher für QProcess, der von QProcessTask verwendet wird

QtTaskTree::QSequentialTaskTreeRunner

Ein sequenzieller Ausführungscontroller für Aufgabenbäume

QtTaskTree::QSingleTaskTreeRunner

Ein Ausführungssteuerer für einen einzelnen Aufgabenbaum

QtTaskTree::QStartedBarrier

Ein gestarteter QBarrier mit einem vorgegebenen Limit

QtTaskTree::QSyncTask

Führt einen benutzerdefinierten Handler synchron zwischen anderen Aufgaben aus

QtTaskTree::QTaskInterface

Hilfsklasse, die bei der Anpassung der Schnittstelle benutzerdefinierter Aufgaben verwendet wird

QtTaskTree::QTaskTree

Führt den Baum asynchroner Aufgaben aus, die deklarativ definiert wurden

QtTaskTree::QTcpSocketWrapper

Ein Wrapper um QTcpSocket

QtTaskTree::QThreadFunction

Eine Klassenvorlage, die die Ausführung einer Funktion in einem separaten Thread über QtConcurrent::run() steuert

QtTaskTree::QThreadFunctionBase

Eine Basisklasse für die Klassenvorlage `QThreadFunction`

QtTaskTree::RepeatIterator

Iterator für Wiederholungen zur Verwendung innerhalb des „For“-Elements

QtTaskTree::Storage

Eine Klassenvorlage für den benutzerdefinierten Datenaustausch im laufenden Aufgabenbaum

QtTaskTree::Then

Ein „then“-Element, das in bedingten Ausdrücken verwendet wird

QtTaskTree::UntilIterator

Bedingter Iterator zur Verwendung innerhalb des „For“-Elements

QtTaskTree::When

Ein Element, das die Ausführung eines Hauptteils bis zum Erreichen einer Barriere verzögert

Ausführliche Beschreibung

Verwenden Sie die TaskTree-Bibliothek, um Rezepte zu erstellen, die beschreiben, welche asynchronen Aufgaben ausgeführt werden sollen, und nutzen 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>, sowie ob sie nacheinander oder parallel ausgeführt werden sollen. Innerhalb von Rezepten können Sie unterschiedliche Fortsetzungspfade definieren, je nachdem, ob die vorherige Aufgabe erfolgreich oder mit einem Fehler abgeschlossen wurde. Es ist auch möglich, Aufgaben in „ Group “-Elementen zu verschachteln, und jedes „ Group “ kann seine Aufgaben entsprechend seinem eigenen Ausführungsmodus oder seiner Workflow-Richtlinie ausführen. Die Rezepte bilden die Aufgabenbaumstrukturen.

Asynchrone Aufgaben

Eine asynchrone Aufgabe ist jede Aufgabe, die gestartet werden kann und später entweder erfolgreich oder mit einem Fehler endet. „Später“ bedeutet, dass nach dem Start der Aufgabe die Kontrolle an die laufende Ereignisschleife zurückgegeben wird. Der aufrufende Thread wird nicht blockiert, bis die Aufgabe beendet ist. Um den Aufgabenbaum nutzen zu können, muss eine Ereignisschleife laufen.

Beispiele für asynchrone Aufgaben:

Rezept- und Aufgabenbaum

Um sich zu merken, was das Rezept und der Aufgabenbaum sind, nutzen wir die Analogie zu Spielkassette und Spieler. Wenn wir ein Rezept schreiben, ist das so, als würden wir eine Spielkassette erstellen; wir erstellen also lediglich eine detaillierte Beschreibung dessen, was der Spieler später tun soll, wenn die Spielkassette in den Spieler (Aufgabenbaum) eingelegt und gestartet wird. Das Rezept selbst ist lediglich eine deklarative Beschreibung für den Aufgabenbaum, die festlegt, was der Aufgabenbaum tun soll, wenn das Rezept an den Aufgabenbaum übergeben und dieser gestartet wird. Das Rezept selbst führt ohne einen Aufgabenbaum keine eigenständigen Aktionen aus – es ist wie eine Kassette ohne Abspielgerät.

Hier ist eine kurze Zusammenfassung der Zuständigkeiten von Rezept und Aufgabenbaum.

Das Rezept (die Kassette) beschreibt:

  • welche Aufgaben vom laufenden Aufgabenbaum dynamisch erstellt werden sollen (über QCustomTask)
  • in welcher Reihenfolge
  • welche Datenstrukturen vom laufenden Aufgabenbaum dynamisch erstellt werden sollen (über „ Storage “)
  • wie jede Aufgabe vor dem Start einzurichten ist
  • wie Daten erfasst werden, wenn Aufgaben abgeschlossen sind
  • Ausführungsmodus (sollen die Aufgaben nacheinander oder parallel ausgeführt werden)
  • Workflow-Richtlinie

Aufgabenbaum (Player):

  • liest das Rezept ein und erstellt automatisch Aufgaben und Datenstrukturen
  • verwaltet die Lebensdauer der erstellten Aufgaben und Datenstrukturen
  • führt Fortsetzungen aus
  • wählt je nach den Ergebnissen abgeschlossener Aufgaben und den Workflow-Richtlinien verschiedene Pfade aus
  • liefert grundlegende Informationen zum Fortschritt

Benutzerdefinierte Aufgaben

Da das Rezept eine Beschreibung für den Aufgabenbaum darstellt, welche Aufgaben beim Start des Aufgabenbaums erstellt werden sollen, können wir diese Aufgaben nicht direkt innerhalb eines Rezepts erstellen. Stattdessen benötigen wir eine deklarative Methode, um dem Aufgabenbaum mitzuteilen, dass er diese Aufgaben zu einem späteren Zeitpunkt für uns erstellen und starten soll. Wenn wir beispielsweise möchten, dass der Aufgabenbaum „ QProcess “ erstellt und startet, beschreiben wir dies, indem wir ein „ QProcessTask “-Element in ein Rezept einfügen. „ QProcessTask “ ist ein Alias für „ QCustomTask “ <QProcess>. Jeder Aufgabentyp sollte seinen entsprechenden „ QCustomTask “ <Type> bereitstellen, damit er in Rezepten verwendet werden kann.

Die folgende Tabelle zeigt einige integrierte benutzerdefinierte Aufgaben, die direkt in Rezepte eingefügt werden können:

Benutzerdefinierte Aufgabe (in Rezepten verwendet)Aufgabenklasse (erstellt durch den ausgeführten Aufgabenbaum)Kurzbeschreibung
QProcessTaskQProcessStartet den Prozess.
QThreadFunctionTask<ReturnType>QThreadFunction<ReturnType>Startet eine asynchrone Aufgabe, die in einem separaten Thread ausgeführt wird.
QTaskTreeTaskQTaskTreeStartet einen verschachtelten Aufgabenbaum.
QNetworkReplyWrapperTaskQNetworkReplyWrapperStartet einen Netzwerk-Download.
QTcpSocketWrapperTaskQTcpSocketWrapperStellt eine TCP-Verbindung her.

Weitere Informationen dazu, wie bestimmte Aufgaben für die Verwendung in Rezepten angepasst werden können, finden Sie unter QTaskInterface und Task Adapters.

Beispielrezept

Die Datei „ QTaskTree “ enthält ein Element der obersten Ebene vom Typ „ Group “ (auch als „Rezept“ bezeichnet), das eine beliebige Anzahl von Aufgaben verschiedener Typen enthalten kann, wie beispielsweise „ 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. Nachdem „ taskTree->start() “ aufgerufen wurde, 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. Sobald die Netzwerkaufgabe erfolgreich abgeschlossen ist, wird schließlich die Aufgabe „ QThreadFunction “ <int> gestartet.

Wenn die zuletzt ausgeführte Aufgabe erfolgreich beendet wurde, gilt der Aufgabbaum als erfolgreich ausgeführt, und das Signal „ QTaskTree::done()“ wird mit „ DoneWith::Success “ ausgesendet. Wenn eine Aufgabe mit einem Fehler endet, wird die Ausführung des Aufgabbaums gestoppt und die verbleibenden Aufgaben werden übersprungen. Der Aufgabbaum endet mit einem Fehler und sendet das Signal „ QTaskTree::done()“ mit „ DoneWith::Error “.

Gruppen

Der übergeordnete Prozess von ` Group ` betrachtet diese als eine einzelne Aufgabe. Wie andere Aufgaben auch kann die Gruppe gestartet werden und erfolgreich oder mit einem Fehler beendet werden. Die Elemente ` 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 enthält, die den „ QProcessTask “ und den „ QThreadFunctionTask “ <int> umfasst. Die Untergruppe ist ein gleichrangiges Element des „ QNetworkReplyWrapperTask “ in der Wurzel. Die Untergruppe enthält ein zusätzliches „ parallel “-Element, das den „ Group “ anweist, seine Aufgaben parallel auszuführen.

Wenn also der „ QTaskTree “ das oben genannte Rezept startet, werden die „ QProcess “ und „ QThreadFunction “ <int> sofort gestartet und laufen parallel. Da die Stammgruppe kein „ parallel “-Element enthält, werden ihre direkten untergeordneten Aufgaben nacheinander ausgeführt. Somit startet der „ QNetworkReplyWrapper “, sobald die gesamte Untergruppe abgeschlossen ist. Die Gruppe gilt als abgeschlossen, wenn alle ihre Aufgaben beendet sind. Die Reihenfolge, in der die Aufgaben beendet werden, ist dabei unerheblich.

Je nachdem, welche Aufgabe länger dauert (QProcess oder QThreadFunction<int>), können also folgende Szenarien eintreten:

Szenario 1Szenario 2
Die Root-Gruppe startetDie Root-Gruppe startet
Untergruppe startetUntergruppe startet
QProcess beginntQProcess startet
QThreadFunction<int> startetQThreadFunction<int> startet
......
QProcess endetQThreadFunction<int> endet
......
QThreadFunction<int> beendetQProcess beendet
Untergruppe endetUntergruppe beendet
QNetworkReplyWrapper startetQNetworkReplyWrapper startet
......
QNetworkReplyWrapper endetQNetworkReplyWrapper endet
Stammgruppe wird beendetRoot-Gruppe wird beendet

Die Unterschiede zwischen den Szenarien sind fett markiert. Drei Punkte bedeuten, dass zwischen dem vorherigen und dem nächsten Ereignis eine nicht näher angegebene Zeitspanne vergeht (eine oder mehrere Aufgaben laufen weiter). Fehlen Punkte zwischen den Ereignissen, bedeutet dies, dass sie synchron ablaufen.

Die dargestellten Szenarien gehen davon aus, dass alle Aufgaben erfolgreich ausgeführt werden. Wenn eine Aufgabe während der Ausführung fehlschlägt, endet der Aufgabenbaum mit einem Fehler. Insbesondere wenn „ 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.

Aufgabenhandler

Verwenden Sie „ Task “-Handler, um eine Aufgabe zur Ausführung einzurichten und das Auslesen der Ausgabedaten der Aufgabe zu ermöglichen, wenn diese erfolgreich oder mit einem Fehler abgeschlossen wird.

Start-Handler einer Aufgabe

Wenn ein Aufgabenobjekt erstellt wird und bevor es gestartet wird, ruft der Aufgabenstrukturbaum einen optional vom Benutzer bereitgestellten Setup-Handler auf. Der Setup-Handler sollte immer eine Referenz auf das zugehörige Aufgabenklassenobjekt entgegennehmen:

const auto onSetup = [](QProcess &process) {
    process.setProgram("sleep");
    process.setArguments({"3"});
};
const Group root {
    QProcessTask(onSetup)
};

Sie können das übergebene ` QProcess ` im Setup-Handler anpassen, damit der Aufgabenbaum den Prozess entsprechend Ihrer Konfiguration starten kann. Sie sollten ` process.start(); ` im Setup-Handler nicht aufrufen, da der Aufgabenbaum diese Funktion bei Bedarf selbst aufruft. Der Setup-Handler ist optional. Wenn er verwendet wird, muss er das erste Argument des Konstruktors der Aufgabe sein.

Optional kann der Setup-Handler einen ` SetupResult` zurückgeben. Der zurückgegebene ` SetupResult ` beeinflusst das weitere Startverhalten einer bestimmten Aufgabe. Mögliche Werte sind:

SetupResult WertKurzbeschreibung
ContinueDie Aufgabe wird normal gestartet. Dies ist das Standardverhalten, wenn der Setup-Handler keinen ` SetupResult ` zurückgibt (d. h., sein Rückgabetyp ist ` void`).
StopWithSuccessDie Aufgabe wird nicht gestartet und meldet ihrem übergeordneten Element den Erfolg.
StopWithErrorDie Aufgabe wird nicht gestartet und meldet ihrem übergeordneten Element einen Fehler.

Dies ist nützlich, um eine Aufgabe nur dann auszuführen, wenn eine Bedingung erfüllt ist und die zur Auswertung dieser Bedingung erforderlichen Daten erst bekannt sind, wenn zuvor gestartete Aufgaben abgeschlossen sind. Auf diese Weise entscheidet der Setup-Handler dynamisch, ob die entsprechende Aufgabe normal gestartet oder übersprungen und ein Erfolg oder ein Fehler gemeldet wird. Weitere Informationen zum Datenaustausch zwischen Aufgaben finden Sie unter Storage.

„Done“-Handler der Aufgabe

Wenn eine laufende Aufgabe beendet ist, ruft der Aufgabenbaum einen optional bereitgestellten „Done“-Handler auf. Der Handler sollte eine const -Referenz auf das zugehörige Aufgabenklassenobjekt entgegennehmen:

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` erfassen und diese zur weiteren Verarbeitung speichern oder zusätzliche Aktionen ausführen.

Hinweis: Wenn der Setup-Handler der Aufgabe „ StopWithSuccess “ oder „ StopWithError “ zurückgibt, wird der „done“-Handler nicht aufgerufen.

Gruppen-Handler

Ähnlich wie bei Aufgaben-Handlern ermöglichen Gruppen-Handler es Ihnen, eine Gruppe einzurichten, um weitere Aktionen auszuführen und anzuwenden, wenn die gesamte Gruppe erfolgreich oder mit einem Fehler abgeschlossen wurde.

Start-Handler einer Gruppe

Der Aufgabenbaum ruft den Start-Handler der Gruppe auf, bevor er die untergeordneten Aufgaben startet. Der Gruppen-Handler nimmt keine Argumente entgegen:

const auto onSetup = [] {
    qDebug() << "Entering the group";
};
const Group root {
    onGroupSetup(onSetup),
    QProcessTask(...)
};

Der Handler für die Gruppeneinrichtung ist optional. Um einen Handler für die Gruppeneinrichtung zu definieren, fügen Sie einem Gruppenelement ein ` onGroupSetup()`-Element 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 Start-Handler der Aufgabe kann auch der Gruppen-Start-Handler ` SetupResult` zurückgeben. Der zurückgegebene Wert von ` SetupResult ` beeinflusst das Startverhalten der gesamten Gruppe. Wenn Sie keinen Gruppen-Start-Handler angeben oder dessen Rückgabetyp `void` ist, lautet die Standardaktion der Gruppe ` Continue`, sodass alle Aufgaben normal gestartet werden. Andernfalls, wenn der Start-Handler „ StopWithSuccess “ oder „ StopWithError “ zurückgibt, werden die Aufgaben nicht gestartet (sie werden übersprungen) und die Gruppe selbst meldet je nach Rückgabewert entweder einen Erfolg oder einen Fehler.

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 eigenen Setup-Handler. Das folgende Szenario geht davon aus, dass alle gestarteten Prozesse erfolgreich beendet werden:

SzenarioKommentar
Starten der StammgruppeGibt kein „SetupResult“ zurück, daher werden ihre Aufgaben ausgeführt.
Gruppe 1 startetGibt „Continue“ zurück, daher werden ihre Aufgaben ausgeführt.
Prozess 1 startet
......
Prozess 1 wird beendet (erfolgreich)
Gruppe 1 wird beendet (erfolgreich)
Gruppe 2 startetGibt „StopWithSuccess“ zurück, sodass Prozess 2 übersprungen wird und Gruppe 2 Erfolg meldet.
Gruppe 2 ist abgeschlossen (erfolgreich)
Gruppe 3 beginntGibt „StopWithError“ zurück, sodass Prozess 3 übersprungen wird und Gruppe 3 einen Fehler meldet.
Gruppe 3 ist beendet (Fehler)
Die Stammgruppe wird beendet (Fehler)Gruppe 3, die ein direktes Kind der Stammgruppe ist, wurde mit einem Fehler beendet, daher bricht die Stammgruppe die Ausführung ab, überspringt Prozess 4, der noch nicht gestartet wurde, und meldet einen Fehler.

Der „Done“-Handler der Gruppen

Der „Done“-Handler einer „ Group “ wird nach der erfolgreichen oder fehlgeschlagenen Ausführung ihrer Aufgaben ausgeführt. Der von der Gruppe gemeldete Endwert hängt von ihrem „ Workflow Policy “ ab. Der Handler kann weitere erforderliche Aktionen ausführen. Der „Done“-Handler wird innerhalb des Elements „ onGroupDone()“ einer Gruppe definiert. Er kann das optionale Argument „ DoneWith “ entgegennehmen, 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 einen onGroupDone() zu einer Gruppe hinzufügen, wird zur Laufzeit ein Assert ausgelöst, das eine Fehlermeldung enthält.

Hinweis: Selbst wenn der Einrichtungs-Handler der Gruppe „ StopWithSuccess “ oder „ StopWithError “ zurückgibt, wird der „done“-Handler der Gruppe aufgerufen. Dieses Verhalten unterscheidet sich von dem des „done“-Handlers einer Aufgabe und kann sich in Zukunft ändern.

Weitere Gruppenelemente

Eine Gruppe kann weitere Elemente enthalten, die den Verarbeitungsablauf beschreiben, wie beispielsweise „ execution mode “ oder „ workflow policy “. Sie kann auch Speicherelemente enthalten, die für das Sammeln und Teilen benutzerdefinierter gemeinsamer Daten zuständig sind, die während der Ausführung der Gruppe erfasst wurden.

Ausführungsmodus

Das Element „Ausführungsmodus“ in einer Gruppe legt fest, wie die direkten untergeordneten Aufgaben der Gruppe gestartet werden. Die gängigsten Ausführungsmodi sind „ sequential “ und „ parallel “. Es ist außerdem möglich, die maximale Anzahl parallel ausgeführter Aufgaben mithilfe des Elements „ ParallelLimit “ festzulegen.

In allen Ausführungsmodi startet eine Gruppe die Aufgaben in der Reihenfolge, in der sie aufgeführt sind.

Ist ein untergeordnetes Element einer Gruppe selbst eine Gruppe, führt die untergeordnete Gruppe ihre Aufgaben gemäß ihrem eigenen Ausführungsmodus aus.

Workflow-Richtlinie

Das Element „Workflow-Richtlinie“ in einem „ Group “ legt fest, wie sich die Gruppe verhalten soll, wenn eine der Aufgaben ihrer direkten untergeordneten Gruppen abgeschlossen ist. Eine detaillierte Beschreibung der möglichen Richtlinien finden Sie unter WorkflowPolicy.

Ist ein untergeordnetes Element einer Gruppe ebenfalls eine Gruppe, führt die untergeordnete Gruppe ihre Aufgaben gemäß ihrer eigenen Workflow-Richtlinie aus.

Speicher

Verwenden Sie das Element „ Storage “, um Informationen zwischen Aufgaben auszutauschen. Dies gilt insbesondere im sequenziellen Ausführungsmodus, wenn eine Aufgabe Daten von einer anderen, bereits abgeschlossenen Aufgabe benötigt, bevor sie starten kann. Ein Aufgabenbaum, der Daten kopiert, indem er sie aus einer Quelle liest und an ein Ziel schreibt, könnte beispielsweise wie folgt aussehen:

static QByteArray load(const QString&fileName) {... }
static void save(const QString&fileName, const QByteArray&array) {... }

static Group copyRecipe(const QString&source, const QString&destination)
{
    struct CopyStorage { // [1] benutzerdefinierte Struktur für den Datenaustausch zwischen Tasks
        QByteArray content; // [2] benutzerdefinierte taskübergreifende Daten
    };

    // [3] Instanz einer benutzerdefinierten taskübergreifenden Struktur, die über den Taskbaum verwaltet werden kann
    const Storage<CopyStorage> storage;

    const auto onLoaderSetup = [source](QThreadFunction<QByteArray> &async) {
        async.setThreadFunctionData(&load, source);
    };
   // [4] Laufzeit: Der Taskbaum aktiviert die Instanz aus [7], bevor er den Handler aufruft
    const auto onLoaderDone = [storage](const QThreadFunction<QByteArray> &async) {
        storage->content = async.result(); // [5] Der Loader speichert das Ergebnis im Speicher
    };

   // [4] Laufzeit: Der Aufgabenbaum aktiviert die Instanz aus [7], bevor der Handler aufgerufen wird
    const auto onSaverSetup = [storage, destination](QThreadFunction<void> &async) {
        const QByteArray content = storage->content; // [6] Der Saver übernimmt Daten aus dem Speicher
        async.setThreadFunctionData(&save, destination, content);
    };
    const auto onSaverDone = [](const QThreadFunction<void> &async) {
        qDebug() << "Save done successfully";
    };

    const Group root {
        // [7] Laufzeit: Der Aufgabenbaum erstellt eine Instanz von „CopyStorage“, wenn „root“ aufgerufen wird
        storage,
        QThreadFunctionTask<QByteArray>(onLoaderSetup, onLoaderDone, CallDoneFlag::OnSuccess),
        QThreadFunctionTask<void>(onSaverSetup, onSaverDone, CallDoneFlag::OnSuccess)
    };
    return root;
}

...

const QString source =...;
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 taskübergreifenden Daten aus einer Inhaltsvariablen „ QByteArray “ [2], die in einer benutzerdefinierten Struktur „ CopyStorage “ [1] eingeschlossen ist. Wenn der Loader erfolgreich abgeschlossen ist, speichert er die Daten in einer Variablen „ CopyStorage::content “ [5]. Der Saver verwendet diese Variable dann, um die Speicheraufgabe zu konfigurieren [6].

Damit ein Aufgabenbaum die Struktur „ CopyStorage “ verwalten kann, wird eine Instanz von „ Storage<CopyStorage>“ erstellt [3]. Wird eine Kopie dieses Objekts als untergeordnetes Element der Gruppe eingefügt [7], wird eine Instanz der Struktur „ CopyStorage “ dynamisch erstellt, sobald der Aufgabenbaum in diese Gruppe eintritt. Verlässt der Aufgabenbaum diese Gruppe wieder, wird die vorhandene Instanz der Struktur „ CopyStorage “ zerstört, da sie nicht mehr benötigt wird.

Wenn mehrere Aufgabenbäume, die eine Kopie der gemeinsamen Instanz von ` Storage` <CopyStorage> enthalten, gleichzeitig ausgeführt werden (einschließlich des Falls, dass die Aufgabenbäume in verschiedenen Threads ausgeführt werden), enthält jeder Aufgabenbaum seine eigene Kopie der Struktur ` CopyStorage `.

Sie können von jedem Handler in der Gruppe mit einem Speicherobjekt auf ` CopyStorage ` zugreifen. Dies umfasst alle Handler aller untergeordneten Tasks der Gruppe mit einem Speicherobjekt. Um in einem Handler auf die benutzerdefinierte Struktur zuzugreifen, übergeben Sie die Kopie des Objekts ` Storage<CopyStorage>` an den Handler (beispielsweise in einer Lambda-Erfassung) [4].

Wenn der Aufgabenbaum einen Handler in einem Unterbaum aufruft, der den Speicher enthält [7], aktiviert der Aufgabenbaum seine eigene „ CopyStorage “-Instanz innerhalb des Objekts „ Storage “ <CopyStorage>. Daher kann auf die Struktur „ CopyStorage “ nur innerhalb des Handler-Körpers zugegriffen werden. Um innerhalb von „ Storage “ <CopyStorage> auf den aktuell aktiven „ CopyStorage “ zuzugreifen, verwenden Sie die Methode „ Storage::operator->()“, „ Storage::operator*()“ oder „ Storage::activeStorage()“.

Die folgende Liste fasst zusammen, wie ein Storage-Objekt in den Aufgabenbaum eingebunden wird:

  1. Definieren Sie die benutzerdefinierte Struktur „ MyStorage “ mit benutzerdefinierten Daten [1], [2]
  2. Erstellen Sie eine Instanz des „ Storage “ <MyStorage>-Speichers [3]
  3. Übergeben Sie die Instanz von „ Storage “ <MyStorage> an die Handler [4]
  4. Greifen Sie in den Handlern auf die „ MyStorage “-Instanz zu [5], [6]
  5. Fügen Sie die Instanz von „ Storage “ <MyStorage> in eine Gruppe ein [7]

Klasse „QTaskTree“

QTaskTree führt die Baumstruktur asynchroner Aufgaben gemäß dem vom Stammelement „ Group “ beschriebenen Schema aus.

Da „ QTaskTree “ ebenfalls eine asynchrone Aufgabe ist, kann sie Teil eines anderen „ QTaskTree “ sein. Um ein verschachteltes „ QTaskTree “ innerhalb eines anderen „ QTaskTree “ zu platzieren, fügen Sie das Element „ QTaskTreeTask “ in ein anderes „ Group “-Element ein.

QTaskTree meldet während der Ausführung den Fortschritt abgeschlossener Aufgaben. Der Fortschrittswert wird erhöht, wenn eine Aufgabe beendet, übersprungen oder abgebrochen wird. Wenn „ QTaskTree “ beendet ist und das Signal „ QTaskTree::done()“ ausgelöst wird, entspricht der aktuelle Fortschrittswert dem maximalen Fortschrittswert. Der maximale Fortschrittswert entspricht der Gesamtzahl der asynchronen Aufgaben in einem Baum. Ein verschachteltes „ QTaskTree “ wird als einzelne Aufgabe gezählt, und seine untergeordneten Aufgaben werden im Baum der obersten Ebene nicht gezählt. Gruppen selbst werden nicht als Aufgaben gezählt, ihre Aufgaben jedoch schon. „ 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 bei deren Erstellung, indem Sie einen Speicher-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 Aufgabenbaum eine „ CopyStorage “-Instanz erstellt und bevor ein Handler innerhalb eines Baums aufgerufen wird, ruft der Aufgabenbaum den Handler „ initStorage “ auf, um die Einrichtung von Anfangsdaten für den Speicher zu ermöglichen, die für diesen bestimmten Durchlauf von „ taskTree “ spezifisch sind.

Um zusätzliche Ergebnisdaten aus dem laufenden Baum zu erfassen, lesen Sie diese ebenfalls aus den Speicherinstanzen im Baum aus, wenn diese kurz vor der Löschung stehen. Installieren Sie dazu einen „storage done“-Handler:

Storage<CopyStorage> storage;
const Group root =...; // Speicher wird in der Gruppe von root und innerhalb der Handler platziert
QTaskTree taskTree(root);
auto collectStorage = [](const CopyStorage &storage) {
    qDebug() << "final content" << storage.content;
};
taskTree.onStorageDone(storage, collectStorage);
taskTree.start();

Wenn der laufende Aufgabenbaum im Begriff ist, eine CopyStorage -Instanz zu löschen, ruft der Aufgabenbaum den collectStorage-Handler auf, um das Auslesen der endgültigen Daten aus dem Speicher zu ermöglichen, die für diesen bestimmten Durchlauf von taskTree spezifisch sind.

Aufgabenadapter

Es ist ganz einfach, neue Task-Typen in Rezepte einzubinden. Es reicht aus, einen neuen Task-Alias für die Vorlage „ QCustomTask “ zu definieren und dabei Ihren „ Task “-Typ als erstes Vorlagenargument zu übergeben, etwa so:

class Worker : public QObject
{
public:
    void start() { ... }

signals:
    void done(bool result);
};

using WorkerTask = QCustomTask<Worker>;

Dies funktioniert, sofern die folgenden Bedingungen erfüllt sind:

  1. Ihre Aufgabe ist von ` QObject` abgeleitet.
  2. Ihre Aufgabe verfügt über eine öffentliche start()-Methode, die die Aufgabe startet.
  3. Ihre Aufgabe sendet das Signal `done(bool)` oder `done(DoneResult)` aus, wenn sie beendet ist.

Wenn Ihre Aufgabe diese Bedingungen nicht erfüllt, können Sie sie dennoch an das TaskTree-Framework anpassen, indem Sie ein zweites Template-Argument mit dem benutzerdefinierten Adapter bereitstellen. Nehmen wir an, wir möchten „ QTimer “ für die Verwendung 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>;

Nun können Sie die ` TimerTask ` in Ihren Rezepten wie folgt verwenden:

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 über einen Standardkonstruktor verfügen, und Objekte dieser Klasse sollten frei zerstörbar sein. Es sollte möglich sein, eine laufende Aufgabe zu zerstören, vorzugsweise ohne darauf zu warten, dass die laufende Aufgabe beendet ist (d. h. sicherer, nicht blockierender Destruktor einer laufenden Aufgabe). Um eine nicht blockierende Zerstörung einer Aufgabe zu erreichen, die über einen blockierenden Destruktor verfügt, sollten Sie die Verwendung des optionalen Template-Parameters ` Deleter ` der ` QCustomTask ` (das dritte Template-Argument) in Betracht ziehen.

Task-Tree-Runner

Der Task-Tree-Runner verwaltet die Lebensdauer des zugrunde liegenden „ QTaskTree “, der zur Ausführung des jeweiligen Rezepts verwendet wird.

Die folgende Tabelle fasst die Unterschiede zwischen den verschiedenen Task-Tree-Runner zusammen:

KlassennameBeschreibung
QSingleTaskTreeRunnerVerwaltet die Ausführung einzelner Aufgabenbäume. Die Methode ` QSingleTaskTreeRunner::start()` startet das übergebene Rezept bedingungslos und setzt dabei alle möglicherweise laufenden Aufgabenbäume zurück. Es kann jeweils nur ein Aufgabenbaum ausgeführt werden.
QSequentialTaskTreeRunnerVerwaltet die sequenzielle Ausführung von Aufgabenbäumen. Die Methode ` QSequentialTaskTreeRunner::enqueue()` startet das übergebene Rezept, sofern der Aufgabenbaum-Runner im Leerlauf ist. Andernfalls wird das Rezept in die Warteschlange gestellt. Sobald die aktuelle Aufgabe abgeschlossen ist, führt der Runner das aus der Warteschlange entnommene Rezept sequenziell aus. Es kann jeweils nur ein Aufgabenbaum ausgeführt werden.
QParallelTaskTreeRunnerVerwaltet die parallele Ausführung von Aufgabenbäumen. Die Methode ` QParallelTaskTreeRunner::start()` startet das übergebene Rezept bedingungslos und führt eventuell bereits laufende Aufgabenbäume parallel weiter.
QMappedTaskTreeRunnerVerwaltet die Ausführung zugeordneter Aufgabenbäume. Die Methode „ QMappedTaskTreeRunner::start()“ startet das angegebene Rezept für einen bestimmten Schlüssel bedingungslos. Falls bereits ein anderer Aufgabenbaum mit demselben Schlüssel ausgeführt wird, 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.