Async/await: Sieben Sprachen, vier verschiedene Ausgaben für dasselbe Programm
A Design Space Exploration of Async/Await

Ein einfaches Programm, das eine Log-Zeile im Hintergrund schreibt, liefert in sieben modernen Async-Runtimes vier verschiedene Ergebnisse – und bei drei Varianten sogar sieben unterschiedliche. Die Autoren analysieren neun Design-Dimensionen wie Eagerness, Extent, Destruction und Cancellation, die die Semantik von async/await prägen. Sie zeigen, warum Sprachen wie Swift und Python+Trio trotz ähnlicher Wahl bei Extent unterschiedliche Ausgaben erzeugen, und formalisieren den Designraum in einem Kernkalkül.
Über die sieben Runtimes hinweg erzeugen keine zwei dieselbe Ausgabe für drei Varianten dieses einfachen Programms!
- spankalee
Wow, das ist wirklich hilfreich und kommt genau zur richtigen Zeit!
Ich baue gerade eine neue Sprache mit async/await und musste viele dieser Entscheidungen treffen, aber ich hatte kein so organisiertes Framework, an dem ich mich orientieren konnte. Ich freue mich, klar zu sehen, dass ich mich größtenteils für Trio mit ein bisschen JavaScript entschieden habe.
Die async-Dokumentationsseite meiner Sprache (Zena's): https://zena-lang.dev/guide/async/ Ich denke, ich könnte einen Durchgang machen und versuchen, die Entscheidungspunkte expliziter herauszustellen.
fyi, ich fand diesen Beitrag über Cancellation vom Autor von Trio sehr überzeugend: https://vorpus.org/blog/timeouts-and-cancellation-for-humans... und ich habe das Cancellation-Design von Zena darauf basiert.
Bearbeiten, um hinzuzufügen: Ich wünschte, dies würde JavaScripts AbortSignal im Abschnitt Cancellation enthalten. Nicht weil es gut ist, sondern weil das Übergeben von Cancel-Tokens ein Muster ist, das existiert. Es gibt auch die Dimension, wer abbrechen kann und, wie bei AbortSignal, ob Tasks sich für Cancellation-Checks entscheiden müssen.
- biorach
Es war lange klar, dass es grundlegende Implementierungsentscheidungen gab, die zwischen async-Runtimes wichtig waren, aber _neun_ Design-Dimensionen? Verdammt.
Ich denke, async ist insofern trügerisch, als es wie ein in sich geschlossener und relativ unkomplizierter Aspekt einer Sprache erscheint. Aber es gibt viele Design-Entscheidungen zu treffen und sie alle haben weitreichende Auswirkungen.
Außerdem denke ich, dass die Implikationen vieler dieser Dimensionen nicht vollständig verstanden sind und wir als Gemeinschaft noch versuchen zu verstehen, wie sie sich in Implementierungen auswirken. Hinzu kommt die subtile Natur einiger Implikationen plus die Kombinationen...
Ich denke, ein guter Vergleich ist lexikalischer vs. dynamischer Scope in Programmiersprachen. Dies ist eine Design-Dimension, über die in den Anfangsjahren des Programmiersprachen-Designs ein oder zwei Jahrzehnte lang gestritten wurde. Erst als die Zeit verging und Erfahrungen mit konkreten Implementierungen gesammelt wurden, wurde klar, dass lexikalisches Scoping die Standardwahl sein sollte und dynamisches Scoping auf verschiedene Nischen beschränkt werden sollte.
- jcelerier
Ich fragte mich: "Hoffentlich erlaubt C++ dir, über diese Achsen zu wählen, damit du dir die async-Primitive bauen kannst, die für das jeweilige Problem am besten funktionieren" und dann: ja!
> Wir können C++ keinem bestimmten Designpunkt in der in Tabelle 1 dargestellten Taxonomie zuordnen, weil jede Achse konfigurierbar ist. Obwohl elegant und neutral, macht die Wahl der vollständigen Programmierbarkeit jede Bibliothek zu einer async-DSL; Wissenstransfer zwischen Projekten innerhalb derselben Sprache wird extrem schwierig.
Das ist nicht der Fall, wenn man in Begriffen dieser Achsen denkt und darüber, welche das jeweilige Problem lösen, und nicht an ein bestimmtes Design. Nehmen wir zum Beispiel das einfachste Programm, das man sich vorstellen kann: einen Netzwerk-Videoplayer. Z.B. sendet ein Server dir RTP-Audio- und -Videoframes und du musst sie korrekt wiedergeben, mit einer schönen GUI obendrauf.
Wenn du das so effizient wie möglich machen willst, musst du dir aller möglichen Arten der async-Interoperation bewusst sein:
- Verbinden und Empfangen von Paketen aus dem Netzwerk in einer klassischen Netzwerk-Zustandsmaschine, in der Coroutinen glänzen
- Umgang mit vsync vs. nicht-vsync für die Anzeige des Videoframes
- Konformität mit welchem async-Paradigma auch immer das Hardware-Videodekodierungssystem, das du verwenden willst, dir bietet, z.B. Intel QuickSync vs. VideoToolbox vs. NVDEC...
- Umgang mit dem synchronen Modell der Audiowiedergabe im Pull-Modus
- Umgang mit der Synchronisation zwischen Audio und Video und somit den async-Mustern, die Multithreading unterstützen, da dein Audio-Thread nicht [...]
- biorach
Endlich hat sich jemand die Zeit genommen, all den mühsamen Kram durchzugehen, den ich seit Ewigkeiten versuche und nicht schaffe, in meinem Kopf geradezuhalten.
- hankbond
> Du musst ein JavaScript-Entwickler sein.
und ich habe das persönlich genommen