HiAPI
Blog

Tutorial

hiapi /v1/tasks: Aufgaben hängen oder laufen in ein Timeout – Diagnose- und Retry-Handbuch

Ein Praxis-Handbuch für Entwickler: Hängende asynchrone Aufgaben debuggen, das Polling-Tempo anpassen und entscheiden, wann neu versucht und wann neu eingereicht werden sollte

Oct 31, 2026
hiapi 博客封面图:错位叠放的骨白色纸张如套印偏移,边缘呈炭黑色,中间夹一张锈红色纸页,平面落地投影。

Warum asynchrone Aufgaben hängen und in Timeouts laufen

Eine asynchrone Aufgabe auf /v1/tasks ist ein Vertrag zwischen drei Parteien: deinem Client, der HiAPI-Plattform und der Compute-Ressource, die die Arbeit tatsächlich ausführt. Ein Hänger bedeutet fast immer, dass eine dieser Ebenen auf eine andere wartet, und beim Debuggen muss man zuerst klären, welche Ebene es ist – denn jede Ebene wird anders behoben, und blindes Retryen macht die Lage nur schlimmer.

Der Lebenszyklus, den du im Kopf haben solltest, sieht so aus:

Der Lebenszyklus, Phase für Phase

  1. Submit (Einreichen) – dein Client sendet die Anfrage, die Plattform nimmt sie an und gibt eine Aufgabenkennung zurück.
  2. Enqueue (Einreihen) – die Aufgabe wird eingeplant. Diese Phase wird unter hoher Last am ehesten langsam und ist für dich am wenigsten sichtbar.
  3. Run (Ausführen) – die Compute-Ressource verarbeitet die Aufgabe. Die Dauer hängt hier vollständig von der Arbeitslast ab, nicht von deinem HTTP-Client.
  4. Complete (Abschließen) – ein Endzustand wird festgehalten und ist über dieselbe Aufgabenkennung abrufbar.

Ein „Hänger“ ist selten ein einzelnes Ereignis, sondern meist einer der folgenden Fälle:

  • Die Aufgabe läuft tatsächlich noch. Lange Eingaben, große Ausgaben oder eine volle Warteschlange verlängern Phase 3. Nichts ist kaputt, du bist nur zu ungeduldig.
  • Die Aufgabe ist eingereiht, aber noch nicht gestartet. Phase 2 kann sich lange ziehen und sieht auf deiner Seite genauso aus wie ein Hänger.
  • Dein Client hat zu früh aufgegeben. Du hast das HTTP-Timeout auf die Statusabfrage gesetzt statt auf die Aufgabe, also lief auf der Plattform alles normal, während dein Prozess abbrach.
  • Die Aufgabe hat einen Endzustand erreicht, aber du hast es nicht bemerkt. Deine Polling-Schleife hat vor der Antwort gestoppt, ist abgestürzt oder wurde beendet.
  • Ein echter plattformseitiger Fehler. Seltener, aber er kommt vor, und es ist der einzige Fall, in dem du an plattformweite Retries denken solltest.

Der zentrale Denkfehler

Der häufigste selbstverschuldete Timeout entsteht daraus, zwei Uhren zu verwechseln. Der HTTP-Request, mit dem du den Status abfragst, sollte kurz sein – es ist nur ein Lesevorgang. Die Aufgabe hat ihre eigene, deutlich längere Frist. Wenn du für beides denselben Timeout-Wert verwendest, brichst du gesunde Aufgaben ab und versuchst sie dann erneut, wodurch du fast fertige Arbeit verdoppelst.

Bevor du irgendetwas änderst, bestimme, welcher der fünf oben genannten Fälle tatsächlich vorliegt. Alles Weitere in diesem Handbuch hängt von dieser Antwort ab.

Schritt eins: Beweise zur Aufgabe sammeln

Widerstehe in der ersten Minute dem Impuls, Code zu ändern oder einen Retry zu starten. Ein Hänger ist ein Zustand, den man nur im Nachhinein debuggen kann, wenn man genug Informationen festgehalten hat. Erfasse Folgendes bevor du eingreifst.

Was pro Aufgabe protokolliert werden sollte

  • Die bei der Einreichung zurückgegebene Aufgabenkennung. Sie ist der Schlüssel zu allem; ohne sie kannst du nur raten.
  • Den zuletzt beobachteten Status-String, wörtlich und nicht umformuliert.
  • Zeitstempel: wann du eingereicht hast, wann jede Statusabfrage rausging und wann jede zurückkam.
  • Die Anzahl der Statusabfragen und ihre Abstände.
  • Die Form der Anfrage-Payload – nicht unbedingt der vollständige Inhalt, aber genug zur Reproduktion: Eingabegröße, Parameter und alles, was die Laufzeit beeinflusst.
  • Die clientseitige Frist, die du gesetzt hast (falls vorhanden), und ob sie ausgelöst wurde.

Denselben Bezeichner einmal manuell erneut abfragen

Bevor du irgendeine Schlussfolgerung ziehst, starte eine frische Statusabfrage für dieselbe Aufgabenkennung. Es gibt zwei mögliche Ergebnisse:

  • Der Status hat sich weiterentwickelt. Die Aufgabe lebt; du hast einen Polling- oder Timeout-Bug, keinen Aufgaben-Bug. Gehe zum Abschnitt über Polling.
  • Der Status ist unverändert und die Aufgabe ist schon alt. Jetzt hast du eine echte Verdacht auf eine hängende Aufgabe. Notiere, wie lange sie schon in diesem Zustand verharrt, und vergleiche das mit deiner Erwartung.

Schreib es auf

Führe für jeden Versuch eine kurze Logzeile: Kennung, Status, verstrichene Zeit, getroffene Entscheidung. Wiederkehrende Hänger lassen sich fast immer aus der Historie diagnostizieren, nicht aus dem aktuellen Ereignis. Wenn du nicht rekonstruieren kannst, was eine Aufgabe vor einer Stunde getan hat, wirst du denselben Fehler immer wieder debuggen.

Ein weiteres leicht vergessenes Beweisstück: Tageszeit und Art der Arbeitslast. Wenn sich Hänger auf eine bestimmte Eingabeklasse konzentrieren, hast du ein Muster entdeckt, das es zu melden gilt – keinen sporadischen Fehler, den man retryen sollte.

Aufgabenstatus richtig interpretieren

Der Status ist die Tatsachengrundlage jeder Retry-Entscheidung. Behandle ihn als kleinen Zustandsautomaten, bei dem jeder Zustand genau eine Aktion auslöst, damit du niemals eine noch laufende Aufgabe erneut versuchst.

Die Status, die du siehst, und was sie bedeuten

  • Queued / pending (eingereiht/wartend) – angenommen, nicht gestartet. Lange Verweildauer hier bedeutet Scheduling-Druck. Nicht erneut versuchen; die Aufgabe existiert bereits.
  • Running / processing (läuft/in Bearbeitung) – die Compute-Ressource arbeitet. Alles in Ordnung. Warten ist die richtige Aktion.
  • Succeeded / completed (erfolgreich/abgeschlossen) – Endzustand. Ergebnis lesen, fertig.
  • Failed / error (fehlgeschlagen/Fehler) – Endzustand, dessen Fehler-Payload dein bestes Debug-Material ist. Lies ihn, bevor du irgendetwas entscheidest.
  • Cancelled / expired (abgebrochen/abgelaufen) – ein Endzustand durch Richtlinie oder durch deine eigene Aktion. Ob du erneut versuchst, ist eine völlig neue Entscheidung.
  • Unknown oder fehlend – die Kennung ist nicht erkennbar. Das ist kein Fehlerzustand, sondern ein Beweiszustand. Lies die Kennung erneut und prüfe, ob du die richtige betrachtest.

Zwei Fragen, die du bei jedem Status stellen solltest

  1. Ist das ein Endzustand? Wenn nicht, ist die einzig richtige Aktion, innerhalb deines Budgets weiter zu warten.
  2. Wenn es ein Endzustand ist, ist er wiederholbar? Erfolg ist nicht wiederholbar. Fehler sind vielleicht wiederholbar, vielleicht nicht – lies zuerst den Fehler. Eine unbekannte Kennung erfordert Untersuchung, keinen Retry.

Den Status nicht aus der verstrichenen Zeit ableiten

„Es läuft seit zehn Minuten, also hängt es sicher“ ist eine Vermutung, keine Diagnose. Die verstrichene Zeit sagt dir nur, wann du abfragen sollst, nie, was der Status ist. Eine Aufgabe kann lange in der Warteschlange stehen und dann einwandfrei abschließen. Lass den Status-String die Schlussfolgerung ziehen; deine Uhr bestimmt nur, wie oft du fragst.

Ein vernünftiges Polling-Tempo entwerfen

Die meisten „Hänger“ sind getarnte Polling-Bugs. Es gibt zwei Hauptfehlerarten: zu aggressives Polling, das deine eigene Zuverlässigkeit schädigt, und zu naives Polling, das einen Endzustand nie bemerkt. Ein begrenztes Tempo löst beides.

Langsam starten, sanft zurückweichen

Pollen ist keine enge Schleife. Beginne mit einem kurzen Intervall, damit schnelle Aufgaben zügig ein Ergebnis liefern, und vergrößere das Intervall, während die Aufgabe älter wird:

import time

def poll(job_id, check_status, deadline_s=600):
    start = time.monotonic()
    interval = 1.0
    while True:
        status = check_status(job_id)
        if status in ("succeeded", "failed", "cancelled", "expired"):
            return status
        if time.monotonic() - start > deadline_s:
            return "client_deadline_exceeded"
        time.sleep(interval)
        interval = min(interval * 1.5, 30.0)  # Obergrenze

Entscheidende Eigenschaften: ein anfängliches Intervall, das kurz genug ist, um schnelle Aufgaben zu erfassen, ein Wachstumsfaktor, der lange Aufgaben kostengünstig hält, und eine Obergrenze, die verhindert, dass du eine Stunde schläfst und das Ergebnis verpasst.

Immer eine clientseitige Frist setzen

Gehe nie davon aus, dass die Plattform dir ein Abschlusssignal sendet. Setze deine eigene Frist – die maximale Wartezeit, die du zu warten bereit bist – und behandle deren Überschreitung als eigenständiges Ergebnis, nicht als Fehler. Diese Unterscheidung ist wichtig: Das Ergebnis client_deadline_exceeded bedeutet „ich schaue nicht mehr hin“, nicht „die Aufgabe ist tot“. Protokolliere es entsprechend.

Zwei praktische Regeln

  • Begrenze die Gesamtzahl der Abfragen. Eine Schleife ohne Maximalzahl an Iterationen ist ein Hänger, der nur auf seinen Auftritt wartet. Berechne sie aus deiner Frist und der Intervall-Obergrenze.
  • Trenne das Lese-Timeout von der Aufgabenfrist. Der HTTP-Aufruf zum Statusabruf sollte einen eigenen kurzen Timeout haben. Wenn das Statuslesen in ein Timeout läuft, wiederhole das Lesen – niemals die Aufgabe.

Den Endpunkt respektieren

Polling ist billig, aber nicht kostenlos. Wenn du Hunderte von Aufgaben prüfst, staffle sie und verwende denselben Client wieder. Im Zweifel ist langsamer fast immer die sicherere Richtung: Eine Aufgabe, die sechs Minuten wert ist, ist auch sechs Minuten und dreißig Sekunden wert.

Zwischen Retry und erneutem Einreichen entscheiden

Hier prallen Korrektheit und Kosten aufeinander. Dieselbe Aufgabe zu retryen und eine neue Aufgabe einzureichen ist nicht austauschbar; die falsche Wahl dupliziert entweder Arbeit oder lässt eine gesunde Aufgabe liegen.

Entscheidungsregeln

Gehe sie der Reihe nach durch:

  1. Ist die Aufgabe in einem Endzustand? Wenn nicht, weder retryen noch neu einreichen. Warten. Eine noch laufende Aufgabe zu retryen verdoppelt die Arbeit.
  2. Wenn ja, was sagt der Status? Erfolg bedeutet, du bist fertig. Fehler tragen eine Fehler-Payload – lies sie zuerst.
  3. Könnte der Fehler vorübergehend sein? Timeouts unter hoher Last, kurzzeitige Nichtverfügbarkeit, Rate-Limiting – das sind Retry-Kandidaten.
  4. Könnte der Fehler dauerhaft sein? Ungültige Eingaben, nicht unterstützte Parameter, fehlerhafte Payloads – sie werden bei jedem Versuch gleich scheitern. Korrigiere die Anfrage.

Dieselbe Aufgabe retryen, wenn …

Die Aufgabe existiert und ist wegen eines Grundes in einen fehlgeschlagenen Endzustand übergegangen, der sich wahrscheinlich nicht wiederholt, und die Semantik der Plattform für diese Aufgabe erlaubt einen weiteren Versuch. Du verwendest die Kennung wieder, behältst eine Historie und vermeidest eine zweite Aufgabe, die dasselbe tut.

Als neue Aufgabe erneut einreichen, wenn …

  • die Kennung unbekannt oder nicht mehr abrufbar ist – es gibt nichts mehr zu retryen.
  • die ursprüngliche Aufgabe per Richtlinie ausdrücklich abgelaufen ist und ein Retry keinen Anknüpfungspunkt hat.
  • du die Eingabe ändern musst. Ein Retry ist dieselbe Anfrage; eine Korrektur ist eine neue Anfrage. Lass niemals zu, dass ein Retry etwas anderes bedeutet als die ursprüngliche Anfrage.

Die Asymmetrie der Kosten

Ein Retry einer noch laufenden Aufgabe verdoppelt den Aufwand für ein Ergebnis. Ein erneutes Einreichen ist eine klar neue Aufgabe – neue Kennung, neuer Lebenszyklus und neue Kosten. Beides sind legitime Werkzeuge; beide sind mit Bedacht einzusetzen. Faustregel: Retrye bei Unsicherheit über die Infrastruktur, reiche neu ein bei Sicherheit über die Eingabe.

Retries sicher machen

Ein Retry, der still Arbeit dupliziert, ist schlimmer als ein Hänger, weil er still scheitert und dich Geld kostet. Baue Sicherheit ein, bevor du sie brauchst.

In Idempotenz denken

Frage bei jeder Aufgabe, die du ausführst: Wenn sie zweimal läuft, ist das Ergebnis dann immer noch korrekt? Wenn die Antwort nein ist – etwa weil die Aufgabe eine Zeile anhängt oder einen nachgelagerten Seiteneffekt auslöst –, muss deine Retry-Logik durch den Status begrenzt sein, nie durch einen Timer. Retrye nur Endzustands-Fehler und erst, nachdem eine frische Statusabfrage diesen Status bestätigt hat.

Versuche begrenzen

Jede Retry-Schleife braucht eine maximale Anzahl an Versuchen und eine maximale Gesamtdauer, beide explizit geprüft. Eine unbegrenzte Retry-Schleife ist nur ein langsamerer und teurerer Hänger. Wenn das Budget erschöpft ist, lege den Fehler offen – verschlucke ihn nicht still.

Zwischen den Versuchen Backoff einbauen

Auch die Retry-Versuche selbst sollten auseinandergezogen werden und mit jedem Fehlschlag wachsen. Eine bereits ausgefallene Abhängigkeit sofort zu retryen scheitert meist erneut und belastet ein ohnehin schon angespanntes System zusätzlich.

Versuche protokollieren, nicht nur Fehler

Protokolliere jeden Versuch: Aufgabenkennung, Status zum Entscheidungszeitpunkt, Versuchsnummer, verwendetens Intervall und Ergebnis. Das ist der Unterschied zwischen „es hängt manchmal“ und „es hängt dienstagmorgens bei großen Eingaben“. Letzteres ist behebbar, Ersteres nicht.

Echte Fehler niemals verdecken

Wenn eine Aufgabe dreimal gescheitert ist und deine Wrapper-Schicht nur einen pauschalen Fehler zurückgibt, hast du die Fehler-Payloads verworfen, die die Ursache erklären. Reiche den letzten Status und die Fehlerdetails bis zum Aufrufer durch. Retry ist ein Wiederherstellungsmechanismus, kein Verschleierungsmechanismus – er soll die Chance verringern, dass das zugrunde liegende Problem auftritt, nicht die Chance, dass es gesehen wird.

Checkliste zur Nachbetrachtung wiederkehrender Hänger

Tritt ein Hänger einmal auf, behebe einfach die aktuelle Aufgabe. Tritt er zweimal auf, gehe diese Checkliste durch – sonst zahlst du ewig für Retries.

Checkliste

  • Klassifiziere das Ereignis. War es eine noch laufende Aufgabe, bei der du aufgehört hast hinzusehen, eine wirklich festgefahrene Aufgabe, ein Client-Timeout oder ein Status, den du nicht lesen konntest? Wenn du es benennen kannst, ist die Hälfte schon gelöst.
  • Prüfe die Polling-Berechnung. Greifen deine Intervall-Obergrenze, deine Frist und deine Versuchsgrenze tatsächlich? Ist irgendeine Schleife unbegrenzt?
  • Stelle sicher, dass die Timeouts getrennt sind. Wurde dein Statuslese-Timeout jemals mit der Aufgabenfrist verwechselt?
  • Überprüfe die Retry-Entscheidungen. Wurde jemals ein Retry für eine Nicht-Endzustands-Aufgabe gestartet? Wurde jemals durch erneutes Einreichen eine noch laufende Aufgabe dupliziert?
  • Prüfe die Fehler-Payloads. Wurden die Fehler bewahrt oder von der Wrapper-Schicht verschluckt?
  • Suche nach Eingabemustern. Häufen sich die Fehler nach Payload-Größe, Parametern oder Art der Arbeitslast?
  • Überdenke die Erwartung. Überschreitet die Aufgabe deine Frist, weil die Arbeit tatsächlich so groß ist? Dann liegt der Bug in der Frist.

Erkenntnisse in Standardkonfiguration umwandeln

Jeder Punkt oben sollte zu einer Standardpraxis in deinem Client werden: ein begrenzter Poller, ein eigenständiges Frist-Ergebnis, eine statusgesteuerte Retry-Strategie und strukturierte Logs mit der Aufgabenkennung als Schlüssel. Dann ist der nächste Hänger nur eine Zeile im Log statt ein verlorener Nachmittag.

Eine asynchrone Aufgabe ist ein Vertrag, und der Vertrag hält, solange du ihren Status respektierst. Unterscheide läuft noch von kann nie abgeschlossen werden, warte in einem begrenzten Tempo und retrye nur, was sicher zu retryen ist – so bleibt ein Hänger ein Ärgernis und wird kein Ausfall.

Ähnliche Artikel

Alle Artikel ansehen →

Starte mit persistenten KI-Artefakten

Hol dir deinen API-Key und rufe Bild-, Video- und Audiomodelle über eine einzige task-basierte API auf.

API-Dokumentation lesen