Workflowausführungsmodi

Beim Ausführen eines Workflows in .NET steuert der Ausführungsmodus, wie Supersteps (Super-Schritte) verarbeitet werden und wie Ereignisse an den Verbraucher übermittelt werden. Die InProcessExecution Klasse macht zwei Ausführungsmodi verfügbar: OffThread und Lockstep.

Übersicht

OffThread (Standard) Sperrschritt
Superstep-Ausführung Hintergrundthread Thread des Verbrauchers
Ereignisübermittlung Unmittelbar, wenn Ereignisse ausgelöst werden Stapelverarbeitung nach Abschluss jedes Supersteps
Schrittausführung Unabhängig von der Ereignisverarbeitung Angehalten, bis gebündelte Ereignisse verarbeitet werden
Parallelität Consumer liest Ereignisse ein, während Supersteps ausgeführt werden Wechsel zwischen Consumer- und Superstep-Ausführung
Am besten geeignet für Echtzeitstreaming, Produktionsszenarien Testen, Debuggen, deterministische Sortierung

OffThread

OffThread ist der Standardausführungsmodus . Supersteps werden in einem Hintergrundthread ausgeführt, und Ereignisse werden sofort gestreamt, wenn sie über eine kanalbasierte Architektur ausgelöst werden.

// OffThread is the default — these are equivalent:
await using StreamingRun run = await InProcessExecution.RunStreamingAsync(workflow, input);
await using StreamingRun run = await InProcessExecution.OffThread.RunStreamingAsync(workflow, input);

So funktioniert es

  1. Eine Hintergrundaufgabe führt fortlaufend Supersteps aus, während Nachrichten ausstehen.
  2. Wenn Executoren Ausgaben oder Ereignisse liefern, werden die resultierenden WorkflowEvent Objekte in ein ungebundenes Channel<WorkflowEvent>Objekt geschrieben.
  3. Der Konsument liest Ereignisse aus dem Kanal WatchStreamAsync und empfängt sie in Echtzeit, sobald sie produziert werden.
  4. Wenn alle Supersteps abgeschlossen sind und keine Nachrichten verbleiben, wird die Ausführung mit einem Idle oder PendingRequests Status angehalten.

Da die Superstep-Schleife und der Consumer parallel laufen, werden Ereignisse sofort angezeigt, sobald sie ausgelöst werden – ohne Verzögerung durch Pufferung. Dies macht OffThread ideal für Streamingszenarien, in denen die Übermittlung von Ereignissen mit geringer Latenz wichtig ist, z. B. das Anzeigen von Token-nach-Token-Updates in einer Benutzeroberfläche.

Parallelausführungen

OffThread unterstützt auch eine gleichzeitige Variante, mit der mehrere Ausführungen dieselbe Workflowinstanz gleichzeitig gemeinsam nutzen können:

await using StreamingRun run = await InProcessExecution.Concurrent.RunStreamingAsync(workflow, input);

Von Bedeutung

Für die gleichzeitige Ausführung müssen alle Ausführenden im Workflow entweder im Konstruktor deklariert oder als Fabrikmethoden bereitgestellt werden.

Sperrschritt

Im Lockstep-Modus werden Supersteps im Thread des Verbrauchers statt auf einer Hintergrundaufgabe ausgeführt. Ereignisse werden während jedes Supersteps gesammelt und nach Abschluss des Supersteps als Batch ausgegeben.

await using StreamingRun run = await InProcessExecution.Lockstep.RunStreamingAsync(workflow, input);

So funktioniert es

  1. Der Consumer ruft auf WatchStreamAsync, was die Ausführungsschleife steuert.
  2. Ein Superstep wird vollständig ausgeführt, und Ereignisse werden in einer Warteschlange gesammelt.
  3. Nach Abschluss des Supersteps werden alle ausstehenden Ereignisse dem Empfänger übergeben.
  4. Der nächste Superstep beginnt erst, nachdem der Verbraucher alle Ereignisse aus dem vorherigen empfangen hat.

Dieses abwechselnde Muster bedeutet, dass der Consumer und die Workflow-Engine nie gleichzeitig ausgeführt werden. Die Ereignisübermittlung ist deterministisch – alle Ereignisse aus einem Superstep werden garantiert vor Ereignissen aus dem nächsten Superstep eintreffen.

Wann man Lockstep verwenden sollte

Lockstep ist nützlich, wenn:

  • Tests – deterministische Ereignisordnung macht Assertionen einfach.
  • Debugging – Schrittweises Debugging ist einfacher, wenn die Ausführung im Thread des Nutzers verbleibt.
  • Sortierte Verarbeitung – Szenarien, in denen Sie die Ereignisse eines Supersteps vollständig verarbeiten müssen, bevor der nächste Superstep beginnt.

Auswählen eines Ausführungsmodus

Für die meisten Produktionsszenarien wird der Standardmäßige OffThread-Modus empfohlen. Es bietet die beste Reaktionsfähigkeit und ermöglicht es dem Workflow, die Verarbeitung fortzusetzen, während der Consumer Ereignisse verarbeitet.

Verwenden Sie Lockstep, wenn deterministisches Verhalten wichtiger ist als Leistung, z. B. bei Unit-Tests oder Debugging-Sitzungen.

// Production: OffThread (default)
await using StreamingRun run = await InProcessExecution.RunStreamingAsync(workflow, input);

// Testing: Lockstep for deterministic behavior
await using StreamingRun run = await InProcessExecution.Lockstep.RunStreamingAsync(workflow, input);

Nicht-Streaming-Ausführung

Beide Ausführungsmodi unterstützen die Nicht-Streaming-Ausführung über RunAsync. Im Nicht-Streaming-Modus wird der Workflow zum Abschluss ausgeführt und sammelt alle Ereignisse in einem Run Objekt, anstatt sie inkrementell zu streamen:

Run run = await InProcessExecution.RunAsync(workflow, input);

// Access all emitted events
foreach (WorkflowEvent evt in run.OutgoingEvents)
{
    // Process events
}

Da die nicht streamende Ausführung alle Ereignisse nach Abschluss erfasst, gilt der Echtzeitvorteil von OffThread nicht. Der Hauptunterschied zwischen den Modi in Nicht-Streaming-Szenarien liegt im Threading: Bei OffThread werden die Supersteps in einem Hintergrundthread ausgeführt, sodass der aufrufende Thread während des Wartens auf die Fertigstellung freigegeben wird. Im Gegensatz dazu werden bei Lockstep die Supersteps im Thread des Aufrufers ausgeführt und blockieren diesen, bis der Workflow beendet ist.

Bei der Ausführung ohne Streaming wird der Standardmäßige OffThread-Modus verwendet. So verwenden Sie Lockstep mit nicht streamingbasierter Ausführung:

Run run = await InProcessExecution.Lockstep.RunAsync(workflow, input);

Nächste Schritte

Ausführungsmodi gelten nicht für Python-Workflows. Python-Workflows verwenden ein einzelnes Ausführungsmodell, das die Superstep-Verarbeitung und Ereignisübermittlung über einen asynchronen Generator abwickelt. Dieses Modell ähnelt dem .NET Lockstep-Modus – Schritte schreiten nur voran, wenn der Consumer aktiv Ereignisse vom Generator abruft.

Informationen zum Ausführen von Python-Workflows finden Sie unter Workflow Builder & Execution.

Beim Ausführen eines Workflows in Go steuert die Ausführungsumgebung, wie Supersteps verarbeitet werden und wie Ereignisse an den Verbraucher übermittelt werden. Das workflow/inproc Paket macht drei Umgebungen verfügbar: Default/OffThread, , Lockstepund Concurrent.

Übersicht

OffThread / Standard Sperrschritt Konkurrent
Superstep-Ausführung Hintergrund-Goroutine Vom Event-Consumer gesteuert Hintergrundgoroutine
Ereignisübermittlung Unmittelbar, wenn Ereignisse ausgelöst werden In Batches, während der Datenstrom verarbeitet wird Unmittelbar, wenn Ereignisse ausgelöst werden
Am besten geeignet für Echtzeitstreaming, Produktionsszenarien Testen, Debuggen, deterministische Sortierung Freigegebene Workflowinstanzen mit gleichzeitig sicheren Bindungen

OffThread

OffThread ist der Standardausführungsmodus. Dies sind gleichwertig:

stream, err := inproc.Default.RunStreaming(ctx, wf, input)
stream, err := inproc.OffThread.RunStreaming(ctx, wf, input)

So funktioniert es

  1. Eine im Hintergrund laufende Goroutine führt Supersteps aus, solange noch Nachrichten ausstehen.
  2. Wenn Executors Ausgaben oder Ereignisse ausgeben, werden Workflow-Ereignisse in den Stream geschrieben.
  3. Der Verbraucher liest Ereignisse mit WatchStreamund empfängt sie, während sie produziert werden.
  4. Wenn alle Supersteps abgeschlossen sind und keine Nachrichten mehr vorhanden sind, endet der Lauf mit dem Status „Leerlauf“ oder „Ausstehende Anforderung“.

Parallelausführungen

Verwenden Sie inproc.Concurrent, wenn alle Executor-Bindungen im Workflow die gemeinsame gleichzeitige Ausführung unterstützen:

stream, err := inproc.Concurrent.RunStreaming(ctx, wf, input)
if err != nil {
    return err
}
defer stream.Close(ctx)

Sperrschritt

Im Lockstep-Modus schreitet die Workflow-Ausführung voran, während der Verbraucher aus dem Datenstrom liest. Dadurch wird die Ereignisbestellung für Tests und Debugging deterministisch.

stream, err := inproc.Lockstep.RunStreaming(ctx, wf, input)
if err != nil {
    return err
}
defer stream.Close(ctx)

for evt, err := range stream.WatchStream(ctx) {
    if err != nil {
        return err
    }
    // inspect event
}

So funktioniert es

  1. Der Consumer ruft auf WatchStream, was die Ausführungsschleife steuert.
  2. Ein Superstep wird bis zum Abschluss ausgeführt, und Ereignisse werden akkumuliert.
  3. Die akkumulierten Ereignisse werden an den Consumer übergeben.
  4. Der nächste Superstep beginnt erst, nachdem der Verbraucher die Ereignisse des vorherigen Supersteps erhält.

Wann man Lockstep verwenden sollte

Verwenden Sie Lockstep, wenn deterministisches Verhalten mehr als das Streaming mit geringer Latenz wichtig ist, z. B. Komponententests, Debugging oder Szenarien, in denen Sie die Ereignisse eines Supersteps vollständig verarbeiten möchten, bevor der nächste Superstep beginnt.

Auswählen eines Ausführungsmodus

Für die meisten Produktionsszenarien verwenden inproc.Default oder inproc.OffThread. Verwenden Sie inproc.Lockstep, wenn die deterministische Ereignisreihenfolge wichtiger ist als die Streaminglatenz, z. B. in Tests. Verwenden Sie inproc.Concurrent nur, wenn jede Bindung im Workflow die gemeinsame gleichzeitige Ausführung unterstützt.

// Production: OffThread (default)
stream, err := inproc.Default.RunStreaming(ctx, wf, input)
if err != nil {
    return err
}
defer stream.Close(ctx)

// Testing: Lockstep for deterministic behavior
testStream, err := inproc.Lockstep.RunStreaming(ctx, wf, input)
if err != nil {
    return err
}
defer testStream.Close(ctx)

Nicht-Streaming-Ausführung

Alle Ausführungsumgebungen unterstützen auch den Nicht-Streaming-Modus Run, der bis zum nächsten Halt ausgeführt wird und die ausgegebenen Ereignisse im zurückgegebenen Ausführungslauf speichert.

run, err := inproc.Default.Run(ctx, wf, input)
if err != nil {
    return err
}

for evt := range run.NewEvents() {
    if output, ok := evt.(workflow.OutputEvent); ok {
        fmt.Printf("Final result: %v\n", output.Output)
    }
}

Nächste Schritte