Zum Inhalt springen

Aufgabe 04-1a - Parallel oder nacheinander?

Zu Zen-Modus wechseln
  • Du koordinierst mehrere Abläufe mit Promise.all, Promise.allSettled und Promise.race und begründest die Wahl fachlich.
  • Du belegst Entscheidungen für oder gegen gleichzeitiges Starten mit Messwerten.
  • Du baust einen Timeout und erklärst, warum er die Arbeit nicht abbricht.
  • Du grenzt asynchrones Warten von echter Parallelität mit Workern ab.

Aus der Pizzeria wird ein Betrieb: Fünf Bestellungen kommen gleichzeitig herein. Backt die Küche eine nach der anderen oder alle auf einmal? Was passiert mit dem ganzen Tisch, wenn eine Pizza verbrennt? Und wie lange wartet ein ungeduldiger Kunde?

Du misst den Unterschied zwischen nacheinander und gleichzeitig, schreibst einen Küchenbericht mit Einzelauswertung und baust einen Timeout (siehe Kapitel Nebenläufigkeit II: Koordination, Einheit 4.1; für die Stufe MandalorianPlus auch Einheit 4.2). Alles landet in kitchen.ts; die Funktion orderPizza(topping) baust du nach dem Muster deiner Pizzeria aus Aufgabe 03-2a: rund 300 ms Backzeit, in 20 Prozent der Fälle throw new Error("<topping>: burned!").

Projektordner anlegen siehe https://qiz.at/projektordner

  • Lies vorher kurz https://qiz.at/assignmentsabgabe durch.
  • Nur vollständige Abgaben (siehe https://qiz.at/rules) können positiv bewertet werden.
  • Abgabe- und Testdetails siehe https://qiz.at/assignmentsabgabe
  • Die Übung liegt als Unterordner uebung-04-1a in deinem Jahres-Repository SJ26_27_4AHITM_SEW_<Benutzername>_exercises, eingerichtet wie in Aufgabe 03-2a ("type": "module", Skripte dev und typecheck). Messwerte und schriftliche Antworten stehen als Kommentar in kitchen.ts. Du arbeitest allein.
  • Fünf Bestellungen nacheinander (Schleife mit await): Gesamtdauer mit performance.now() messen und notieren.
  • Fünf Bestellungen gleichzeitig (Promise.all über orders.map(...)): Gesamtdauer messen. Erklär das Verhältnis der beiden Zeiten in zwei Sätzen.
  • Provozier eine verbrannte Pizza und beobachte: Promise.all reißt alle Bestellungen mit in den Fehlerfall, auch die gelungenen.
  • Ersetz Promise.all durch Promise.allSettled und gib einen Bericht aus, etwa 4 delivered, 1 burned, mit den Gründen der fehlgeschlagenen Bestellungen.
  • Begründe in zwei Sätzen fachlich, warum hier allSettled das richtige Werkzeug ist und wann dagegen all richtig wäre.

Tipp: Damit du die verbrannte Pizza nicht dem Zufall überlassen musst, gib orderPizza wie in Aufgabe 03-2a eine eigene Zufallsfunktion mit. allSettled liefert pro Eintrag ein Objekt mit status; der Grund eines Fehlschlags steht in reason. Das Kriterium für all gegen allSettled steht in Einheit 4.1 als Merksatz.

Vollständig, wenn:

  • beide Messwerte notiert sind und das Verhältnis mit dem langsamsten Promise erklärt ist.
  • der Fehlerfall von Promise.all nachgewiesen ist.
  • der Bericht mit allSettled stimmt, die Gründe nennt und die Wahl fachlich begründet ist.
  • kitchen.ts strikt ohne Fehler und ohne any kompiliert.

Wie unter Allgemein.

Für alle, die in der Einheit mit den Schritten oben fertig sind bzw. optional (= freiwillig), wenn du in der Einheit nicht fertig wirst.

Erweitere (siehe Erweitern in https://qiz.at/rules) den vollständigen Teil der vorherigen Stufe dieser Datei (ändere oder lösche nur, was für diese Aufgabe unbedingt notwendig ist):

  • Implementier withTimeout<T>(promise: Promise<T>, milliseconds: number): Promise<T> mit Promise.race: Dauert der Vorgang länger, scheitert er mit "too slow, order canceled".
  • Teste mit einem Timeout deutlich über und deutlich unter der typischen Backzeit; beide Fälle sind im Log nachweisbar.
  • Erklär in zwei Sätzen, was mit der Pizza passiert, die das Rennen verloren hat, und wie sich AbortController davon unterscheidet.

Tipp: Der Timer-Teil von withTimeout ist ein Promise, das nie erfüllt wird; sein Typ ist Promise<never>. Einheit 4.1 zeigt das Muster.

Vollständig, wenn:

  • withTimeout generisch typisiert ist und in beiden Testfällen das erwartete Ergebnis liefert.
  • die Erklärung nennt, dass der Verlierer weiterläuft, und dass AbortController die Arbeit wirklich beendet.

Wie unter Allgemein.

Für alle, die in der Einheit mit den Schritten oben fertig sind bzw. optional (= freiwillig), wenn du in der Einheit nicht fertig wirst.

Erweitere (siehe Erweitern in https://qiz.at/rules) den vollständigen Teil der vorherigen Stufe dieser Datei (ändere oder lösche nur, was für diese Aufgabe unbedingt notwendig ist):

  • Die Küche hat nur zwei Öfen. Schreib runWithLimit(tasks, limit), das höchstens limit Bestellungen zugleich backt; die übrigen warten, bis ein Ofen frei wird.
  • Miss den Ansturm mit limit 1, 2 und 5 und erklär die drei Werte mit der Zahl der “Runden”, die die Küche braucht.
  • Nenn eine reale Situation, in der genau diese Begrenzung nötig ist, und begründe sie.

Tipp: Übergib keine Promises, sondern Funktionen, die ein Promise liefern (() => orderPizza(topping)); sonst laufen alle Bestellungen schon beim Anlegen der Liste los.

Vollständig, wenn:

  • nie mehr als limit Bestellungen gleichzeitig laufen und alle Ergebnisse ankommen.
  • die drei Messwerte notiert und mit den Runden erklärt sind.
  • die reale Situation stichhaltig ist.

Wie unter Allgemein.

Für alle, die in der Einheit mit den Schritten oben fertig sind bzw. optional (= freiwillig), wenn du in der Einheit nicht fertig wirst.

Erweitere (siehe Erweitern in https://qiz.at/rules) den vollständigen Teil der vorherigen Stufe dieser Datei (ändere oder lösche nur, was für diese Aufgabe unbedingt notwendig ist):

  • Bau in einer zweiten Datei blocking-demo.ts eine bewusst teure synchrone Funktion, etwa eine naive rekursive Fibonacci-Berechnung mit n um 40, und starte dazu eine “Uhr” (setInterval, alle 100 ms ein Zeitstempel). Beobachte und erklär mit dem Event-Loop-Modell, warum die Uhr während der Berechnung steht.
  • Lagere die Berechnung in einen Worker aus (node:worker_threads) und weise nach, dass die Uhr jetzt durchläuft.
  • Beurteile in drei Sätzen den Unterschied zwischen “asynchron” (Warten überbrücken) und “echt parallel” (gleichzeitig rechnen): Wofür genügt der Event Loop, wofür nicht?

Tipp: Miss die Abstände zwischen zwei Uhr-Ticks, dann siehst du den Aussetzer als Zahl. Für npm run typecheck braucht der Import aus node:worker_threads die Typen von Node.js: npm install --save-dev @types/node und in der tsconfig.json "types": ["node"].

Vollständig, wenn:

  • der Aussetzer der Uhr gemessen und mit dem Event-Loop-Modell erklärt ist.
  • die Variante mit Worker nachweislich ohne Aussetzer läuft und dasselbe Ergebnis liefert.
  • die Beurteilung Warten und Rechnen richtig zuordnet.

Wie unter Allgemein.

  1. Wovon hängt die Gesamtdauer von Promise.all ab?
  2. Nach welchem fachlichen Kriterium wählt man zwischen all und allSettled?
  3. Was liefert allSettled pro Eintrag, und wie unterscheidet man Erfolg von Fehlschlag?
  4. Warum stottert die Uhr bei schwerer synchroner Rechenarbeit, obwohl setInterval “asynchron” ist?
  5. Worüber kommunizieren Worker mit dem Hauptthread, und was teilen sie nicht?

Durchschnittlich bis Wesentlich_vollständig: ca. 100 min

  • Oliver Maurhart (MAO)
  • Michael Gabriel (GAM)

CC-BY-SA

https://qiz.at/rules