Note
Access to this page requires authorization. You can try signing in or changing directories.
Access to this page requires authorization. You can try changing directories.
When running a workflow in .NET, the execution mode controls how supersteps are processed and how events are delivered to the consumer. The InProcessExecution class exposes two execution modes: OffThread and Lockstep.
Overview
| OffThread (Default) | Lockstep | |
|---|---|---|
| Superstep execution | Background thread | Consumer's thread |
| Event delivery | Immediate, as events are raised | Batched after each superstep completes |
| Step execution | Independent of event processing | Paused until batched events are consumed |
| Concurrency | Consumer reads events while supersteps run | Consumer and superstep execution alternate |
| Best for | Real-time streaming, production scenarios | Testing, debugging, deterministic ordering |
OffThread
OffThread is the default execution mode. Supersteps run on a background thread, and events stream out immediately as they are raised via a channel-based implementation.
// 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);
How it works
- A background task runs supersteps continuously while messages are pending.
- As executors yield outputs or events, the resulting
WorkflowEventobjects are written to an unboundedChannel<WorkflowEvent>. - The consumer reads events from the channel via
WatchStreamAsync, receiving them in real-time as they are produced. - When all supersteps are complete and no messages remain, the run halts with an
IdleorPendingRequestsstatus.
Because the superstep loop and the consumer run concurrently, events appear as soon as they are raised — there is no buffering delay. This makes OffThread ideal for streaming scenarios where low-latency event delivery matters, such as displaying token-by-token updates in a UI.
Concurrent runs
OffThread also supports a concurrent variant that allows multiple runs to share the same workflow instance simultaneously:
await using StreamingRun run = await InProcessExecution.Concurrent.RunStreamingAsync(workflow, input);
Important
Concurrent execution requires that all executors in the workflow be declared crossRunShareable (on the constructor) or be provided as factory methods.
Lockstep
In Lockstep mode, supersteps run in the consumer's thread rather than on a background task. Events are accumulated during each superstep and emitted as a batch after the superstep completes.
await using StreamingRun run = await InProcessExecution.Lockstep.RunStreamingAsync(workflow, input);
How it works
- The consumer calls
WatchStreamAsync, which drives the execution loop. - A superstep runs to completion, and events are accumulated in a queue.
- After the superstep finishes, all queued events are yielded to the consumer.
- The next superstep begins only after the consumer has received all events from the previous one.
This alternating pattern means the consumer and the workflow engine never run simultaneously. Event delivery is deterministic — all events from a superstep are guaranteed to arrive before any events from the next superstep.
When to use Lockstep
Lockstep is useful when:
- Testing — deterministic event ordering makes assertions straightforward.
- Debugging — step-through debugging is easier when execution stays on the consumer's thread.
- Ordered processing — scenarios where you need to fully process one superstep's events before the next superstep begins.
Choosing an Execution Mode
For most production scenarios, the default OffThread mode is recommended. It provides the best responsiveness and allows the workflow to continue processing while the consumer handles events.
Use Lockstep when deterministic behavior is more important than performance, such as in unit tests or debugging sessions.
// 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);
Non-Streaming Execution
Both execution modes support non-streaming execution via RunAsync. In non-streaming mode, the workflow runs to completion and collects all events into a Run object rather than streaming them incrementally:
Run run = await InProcessExecution.RunAsync(workflow, input);
// Access all emitted events
foreach (WorkflowEvent evt in run.OutgoingEvents)
{
// Process events
}
Because non-streaming execution collects all events after completion, the real-time event delivery benefit of OffThread does not apply. The primary difference between modes in non-streaming scenarios is threading: OffThread runs supersteps on a background thread, freeing the calling thread while awaiting completion, whereas Lockstep runs supersteps on the caller's thread, blocking it until the workflow finishes.
Non-streaming execution uses the default OffThread mode. To use Lockstep with non-streaming execution:
Run run = await InProcessExecution.Lockstep.RunAsync(workflow, input);
Next steps
Execution modes are not applicable to Python workflows. Python workflows use a single execution model that handles superstep processing and event delivery through an asynchronous generator. This model is similar to the .NET Lockstep mode — steps don't advance unless the consumer is actively pulling events from the generator.
For information on running Python workflows, see Workflow Builder & Execution.