Skip to content

5.4. State Machines

Mikołaj Milewski edited this page Nov 12, 2026 · 1 revision

State Machines

A State Machine is a Stateflows Behavior defined as a UML state machine. It processes events according to its active state configuration and transitions. Use it for event-driven lifecycles and interactive processes whose behavior depends on the current state.

stateDiagram-v2
    [*] --> New
    New --> Confirmed : Confirm
Loading

The Stateflows model uses named or typed states, transitions, and pseudostates. Composite states provide hierarchy; orthogonal regions allow multiple active state configurations. The guides below describe how these elements execute in Stateflows.

Browse Concepts for event processing and Building Blocks for states, regions, pseudostates, and transitions.

Define and register a State Machine

Typed runtime states and transitions can inject application services, IStateMachineContext, and common behavior/execution contexts. State callbacks supply IStateContext; transition guards/effects supply ITransitionContext instead. Shared base states and interface-based event handlers avoid repeated lifecycle lambdas and duplicated effect logic. See Dependency Injection for exact availability, reuse examples, and lifetime rules.

This lambda definition has an initial New state and a transition triggered by Confirm:

using Stateflows;
using Stateflows.StateMachines;

public sealed record Confirm;

In the application's startup code:

builder.Services.AddStateflows(stateflows => stateflows
    .AddStateMachines(machines => machines
    .AddStateMachine("Document", machine => machine
        .AddInitialState("New", state => state
            .AddTransition<Confirm>("Confirmed"))
        .AddState("Confirmed"))));

For reusable definitions, implement IStateMachine and register the type:

using Stateflows.StateMachines;

public sealed class Document : IStateMachine
{
    public static void Build(IStateMachineBuilder machine) => machine
        .AddInitialState("New", state => state
            .AddTransition<Confirm>("Confirmed"))
        .AddState("Confirmed");
}

Register the type in the application's startup code:

builder.Services.AddStateflows(stateflows => stateflows
    .AddStateMachines(machines => machines
        .AddStateMachine<Document>("Document")));

Stateflows uses nested fluent builders to define transitions on states, substates inside composite states, and regions inside orthogonal states. Lambda definitions use names; typed overloads can refer to model-element classes.

State Machine capabilities

Need Model element or guide
Represent stable configurations and hierarchy States, Composite States, Orthogonal States, and Regions
React to an event and change configuration Transitions
Continue internal behavior without changing the active state Internal Transition
Perform automatic startup or completion steps Default Transition
Branch or synchronize paths Choice, Junction, Fork, and Join
Restore a prior active configuration History Pseudostate
Delay handling an event while a state is active In-State Event Deferrals
Keep implementation data on a behavior or active state Behavior Values and State-Scoped Values
Embed collaborating behaviors Behavioral Groups
Schedule model-triggered events Scheduling

Transition guards decide whether a candidate transition is enabled; effects run during the transition. See transition evaluation for selection order and transitions for guard/effect definitions.

Event deferral

States can defer configured event types while active. A deferred event is retained for later processing and retried when it is no longer deferred by active states. Deferrals can be conditional; see In-State Event Deferrals for scope and restrictions.

Exception Handling

Exceptions thrown by State Machine behavior code are first passed to registered IStateMachineExceptionHandler hooks. If a handler returns true, the exception is acknowledged and is not automatically sent back as an event. If all handlers return false (or none are registered), Stateflows sends the exception to the same State Machine as a new event, carrying the original event headers. This is a separate, asynchronous delivery; it does not recover the operation that threw, which still fails.

A transition can handle the exception event:

.AddState("Processing", state => state
    .AddTransition<InvalidOperationException>("Failed", transition =>
        transition.AddEffect(async context =>
            await errorLog.RecordAsync(
                context.Event, context.CancellationToken))))
.AddState("Failed");

Exception-triggered transitions match the declared exception type and its derived types. For example, a transition triggered by IOException also matches FileNotFoundException. This matching behavior is specific to exception triggers; do not assume it applies to ordinary event triggers. See Observability and Exception Triggers.

Interacting with State Machines

Locate a State Machine by its class name and string instance ID using IStateMachineLocator, then send events through the behavior handle:

using Stateflows;
using Stateflows.StateMachines;

public sealed class DocumentService(IStateMachineLocator locator)
{
    public async Task ConfirmAsync(string documentId)
    {
        if (!locator.TryLocateStateMachine(
                new StateMachineId("Document", documentId),
                out var document))
        {
            throw new InvalidOperationException(
                $"Document '{documentId}' could not be located.");
        }

        await document.SendAsync(new Confirm());
    }
}

The located handle implements IStateMachineBehavior. In addition to the common IBehavior messaging and lifecycle operations, it provides State Machine-specific status queries and watches:

Method Result Purpose
GetStatusAsync(headers) Task<RequestResult<StateMachineInfo>> Query lifecycle status, expected events, and the current state configuration. Headers are optional.
WatchStatusAsync(Action<StateMachineInfo>) Task<IWatcher> Watch published State Machine status notifications.

Query the current state configuration

StateMachineInfo extends BehaviorInfo with a typed StateMachineId and CurrentStates, an IReadOnlyTree<string> of the current state configuration. A tree, rather than a single state name, represents nesting and simultaneously active orthogonal regions. The inherited BehaviorStatus and ExpectedEvents describe lifecycle status and expected event names.

using Stateflows.Common;

var result = await document.GetStatusAsync();
if (result.Status != EventStatus.Consumed || result.Response is null)
{
    throw new InvalidOperationException(
        $"Could not query the Document: {result.Status}.");
}

var info = result.Response;
Console.WriteLine(info.BehaviorStatus);
foreach (var state in info.CurrentStates.GetAllNodes())
{
    Console.WriteLine(state.Value);
}

These examples use the document handle located above. Keep it typed as IStateMachineBehavior: calling GetStatusAsync through IBehavior instead requests the general BehaviorInfo, without the State Machine-specific return contract.

Watch state configuration changes

await using var watcher = await document.WatchStatusAsync(
    (StateMachineInfo info) =>
    {
        Console.WriteLine(info.BehaviorStatus);
        foreach (var state in info.CurrentStates.GetAllNodes())
        {
            Console.WriteLine(state.Value);
        }
    });

await document.SendAsync(new Confirm());

The explicitly typed callback selects the State Machine-specific overload rather than the inherited BehaviorInfo overload. Notifications report status/configuration snapshots when the engine reports a state change; they are not a callback for every guard, effect, or processed event. Use Observability for internal execution hooks.

The returned IWatcher is a volatile client subscription: keep it and the behavior handle alive while watching, and dispose the watcher when finished. It is not a durable behavior-to-behavior subscription.

Common Behavior operations

The handle also supports SendAsync, RequestAsync, general notification watches, ResetAsync, and FinalizeAsync. Inspect the returned event status and request response rather than assuming that an event was consumed. See Messaging Patterns for delivery, request/response, and subscription semantics.

State Machine definitions can be reused and versioned; see Definition reuse and versions for registration and deployment implications. State Machine observers, interceptors, exception handlers, and visitors are described in Observability.

Clone this wiki locally