|
09.06.2026
08:54 Uhr
|
Qt6 bietet mit QPromise und QFuture mächtige und komfortable Werkzeuge, um asynchrone Operationen unabhängig auszuführen und zu orchestrieren.

Das Framework Qt unterstützt mit seinem tief in der Architektur verankerten Event-System und dem Signal-Slot-Mechanismus seit jeher Entwicklerinnen und Entwickler bei der asynchronen Programmierung. Diese Möglichkeiten hat der vorangegangene Teil unserer Serie vorgestellt.
QFuture ist ein neueres Qt-Konstrukt, das das Konzept des „thennables“ – der .then()-Methoden – aus anderen Sprachen aufnimmt (z. B. Promise in JavaScript, CompletableFuture in Java oder Task in C#). Es erlaubt mit QFuture::then() das Ausführen und Hintereinanderschalten von asynchronen Funktionen, unabhängig von Threads, beispielsweise um Benutzeroberflächen asynchron zu aktualisieren.
In Qt tritt ein lesendes, konsumierendes QFuture immer als die eine Seite der gleichen Medaille auf. Auf der schreibenden, produzierenden Seite steht das QPromise. Entwicklerinnen und Entwickler können den Zustand des Promise-Future-Paares detailliert steuern: starten (QPromise::start()), aussetzen (QPromise::suspend()), beenden (QPromise::finish()) oder abbrechen (QPromise::cancel()). Schließlich können Developer nicht nur einen Rückgabewert, sondern beliebig viele setzen (QPromise::addResult()). Mit der QPromise-/QFuture-API haben sie eine praktische Schnittstelle, um Benutzer- sowie Logik-Schichten effektiv zu trennen und Fortschritte an die Bedienoberfläche zu melden.
Die Klasse QFutureWatcher liefert einen Mechanismus, um die QFuture-Funktionalität mit dem im vorangegangenen Artikel beschriebenen Signal-/Slot-Mechanismus zu verbinden. Das folgende Listing 1 zeigt ein Beispiel für die grundlegenden Funktionsweisen.
Listing 1: Einfaches Beispiel für die Verwendung von QPromise, QFuture und QFutureWatcher.
Neben QFutureWatcher bietet die Klasse QFuture eine Fluent-API an, mit der man Futures hintereinander schaltet (das angesprochene „thennable“ bzw. auch als „chaining“ bezeichnet): Entwickler legen mit then() einen Nachfolger fest, wobei eine zweite Verwendung von then() den bisherigen Nachfolger überschreibt, sodass es immer nur einen Nachfolger geben kann. Außerdem löst die erste Verwendung von addResult des zugrunde liegenden Promise then() aus, und nicht etwa promise.finish(). Ein Abbruch des Promise mit cancel() aktiviert den Callback onCancelled (siehe Listing 2 unten).
Listing 2: Beispiel für das Auslösen von then() und onCancelled().
Die Methode then() ist sowohl beim Parameter als auch dem Rückgabewert generisch. Das bedeutet konkret: Bei QFuture<T> hat das then-Callable einen Parameter vom Typ T. Der Rückgabetyp, den der Entwickler innerhalb des then-Callable verwendet, ist der Rückgabetyp des Futures, das wiederum mit then() in die verkettete Abfolge gehängt werden kann, siehe Listing 3:
Listing 3: Beispiel für Future-Chaining mit then().
Falls innerhalb eines Future oder eines then()-Blocks eine Exception auftritt, wird diese an den Callback onFailed() geleitet. Dabei gilt die Regel, dass die Exception in der Kette weiter wandert, bis sie ein passender onFailed-Callback erreicht. Wenn man Programmteile in Arbeiter-Threads auslagert, muss man dort Exceptions korrekt auffangen und an promise.setException() übergeben. Nur dann funktioniert die Methode onFailed wie erwartet, siehe Listing 4:
Listing 4: Korrekte Verarbeitung von Exceptions in Arbeits-Threads mit Promise.
Entwickler können auch mehrere onFailed mit einem Future verwenden. In diesem Fall wird das erste passende aufgerufen. Dabei ist die Reihenfolge relevant: Beispielsweise wird in Listing 5 das zweite onFailed nie erreicht, da im vorherigen bereits die darüber liegende Klasse abgefangen wurde.
Listing 5: Verwendung mehrerer onFailed-Callbacks
Innerhalb der Funktion, die onFailed verarbeitet, können Entwickler auch einen Rückgabewert angeben. Die Kette wird dann mit diesem Wert fortgeführt, siehe Listing 6:
Listing 6: Fortführung der then()-Kette im Fehlerfall.
Wenn kein passender Callback onFailed vorhanden ist, wird die Exception von demjenigen Thread, in dem sie auftrat, an den aufrufenden Thread weitergeleitet und muss dort entsprechend abgefangen werden (Error Propagation), siehe Listing 7:
Listing 7: Propagation einer Exception.
Die bisher gezeigte Verwendung von then() führt Folgefunktionen standardmäßig in dem Thread aus, in dem das ursprüngliche QFuture lief. Entwickler können aber auch mit einem Parameter den Thread für die Folgefunktion bestimmen. Übergeben sie als Parameter ein QObject, wird dessen zugeordneter QThread verwendet. Bei einen QThreadPoolkommt dieser zum Einsatz.
Schließlich können Developer auch ein Argument vom Typ QtFuture::Launch übergeben: Die weitere Bestimmung Sync entspricht dem beschriebenen Standardfall (gleicher Thread wie Aufrufer), während Async automatisch den globalen Threadpool verwendet und Inherit die Einstellung des Vorgängers, siehe Listing 8:
Listing 8: Bestimmung des Threads mit then(...)
Der Namespace QtFuture enthält darüber hinaus einige nützliche Hilfsfunktionen für Futures. Mit den Funktionen QtFuture::makeReady… erstellen Entwicklerinnen und Entwickler ein Future, das bereits im Status beendet ist (QFuture.isFinished() liefert true). makeReadyVoidFuture erzeugt ein beendetes Future ohne Wert (QFuture<void>), makeReadyValueFuture(T) entsprechend ein QFuture<T> und makeReadyRangeFuture ein beendetes QFuture<T>, das mehrere Ergebnisse besitzt. makeExceptionalFuture schließlich erstellt ein beendetes QFuture, das eine Exception beinhaltet.
Diese Funktionen sind nützlich, wenn man bei einer Funktion anhand von Parametern überprüfen möchte, ob eine Bearbeitung als QFuture überhaupt möglich oder sinnvoll ist. Sofern dies nicht der Fall ist, gibt die Funktion direkt ein beendetes QFuture zurück, siehe Listing 9:
Listing 9: Verwenden eines bei der Erstellung beendeten Futures mit makeReadyValueFuture.
Eine weitere nützliche Funktion ist QtFuture::whenAll(), die ein QFuture erzeugt, das beendet wird, wenn alle als Parameter übergebenen QFuture beendet sind. Die QFuture können verschiedene Template-Typen haben, dementsprechend ist der Rückgabetyp von whenAll ein QFuture mit einer Liste vom Typ std::variant.
Developer müssen hier noch auf die Besonderheit achten, dass beim Chaining mit then() die Nachfolgefunktion auf dem Thread läuft, dessen Future als Letztes beendet wurde. Dies kann, wie bereits beschrieben, ein Parameter für then() explizit festlegen, siehe Listing 10: