You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
docs(async): rewrite with progressive narrative, move to Engine section
Reorganize async.md for clarity:
- Open with key insight: API is the same, engine switches automatically
- Promote initial state activation as first gotcha after basic example
- Simplify engine selection table (4 columns, no definition list)
- Clarify concurrent event sending is async-engine exclusive
- Remove redundant sections (versionadded 2.3.0, "StateChart async
support", duplicate "Asynchronous Support" heading)
Move async from Advanced to Engine in index.md — it's about how the
engine processes callbacks, not an advanced feature.
The {ref}`StateChart` fully supports asynchronous code. You can write async {ref}`actions`, {ref}`guards`, and {ref}`events` triggers, while maintaining the same external API for both synchronous and asynchronous codebases.
9
-
10
-
This is achieved through a new concept called **engine**, an internal strategy pattern abstraction that manages transitions and callbacks.
11
-
12
-
There are two engines, {ref}`SyncEngine` and {ref}`AsyncEngine`.
13
-
14
-
15
-
## Sync vs async engines
16
-
17
-
Engines are internal and are activated automatically by inspecting the registered callbacks in the following scenarios.
18
-
19
-
20
-
```{list-table} Sync vs async engines
21
-
:header-rows: 1
22
-
23
-
* - Outer scope
24
-
- Async callbacks?
25
-
- Engine
26
-
- Creates internal loop
27
-
- Reuses external loop
28
-
* - Sync
29
-
- No
30
-
- SyncEngine
31
-
- No
32
-
- No
33
-
* - Sync
34
-
- Yes
35
-
- AsyncEngine
36
-
- Yes
37
-
- No
38
-
* - Async
39
-
- No
40
-
- SyncEngine
41
-
- No
42
-
- No
43
-
* - Async
44
-
- Yes
45
-
- AsyncEngine
46
-
- No
47
-
- Yes
48
-
49
-
```
50
-
51
-
Outer scope
52
-
: The context in which the state machine **instance** is created.
53
-
54
-
Async callbacks?
55
-
: Indicates whether the state machine has declared asynchronous callbacks or conditions.
56
-
57
-
Engine
58
-
: The engine that will be utilized.
59
-
60
-
Creates internal loop
61
-
: Specifies whether the state machine initiates a new event loop if no [asyncio loop is running](https://docs.python.org/3/library/asyncio-eventloop.html#asyncio.get_running_loop).
62
-
63
-
Reuses external loop
64
-
: Indicates whether the state machine reuses an existing [asyncio loop](https://docs.python.org/3/library/asyncio-eventloop.html#asyncio.get_running_loop) if one is already running.
65
-
66
-
67
-
68
-
```{note}
69
-
All handlers will run on the same thread they are called. Therefore, mixing synchronous and asynchronous code is not recommended unless you are confident in your implementation.
4
+
```{seealso}
5
+
New to statecharts? See [](concepts.md) for an overview of how states,
6
+
transitions, events, and actions fit together.
70
7
```
71
8
72
-
(syncengine)=
73
-
### SyncEngine
74
-
Activated if there are no async callbacks. All code runs exactly as it did before version 2.3.0.
75
-
There's no event loop.
76
-
77
-
(asyncengine)=
78
-
### AsyncEngine
79
-
Activated if there is at least one async callback. The code runs asynchronously and requires a running event loop, which it will create if none exists.
80
-
81
-
82
-
83
-
## Asynchronous Support
9
+
The public API is the same for synchronous and asynchronous code. If the
10
+
state machine has at least one `async` callback, the engine switches to
11
+
{ref}`AsyncEngine <asyncengine>` automatically — no configuration needed.
84
12
85
-
We support native coroutine callbacks using asyncio, enabling seamless integration with asynchronous code. There is no change in the public API of the library to work with asynchronous codebases.
13
+
All statechart features — compound states, parallel states, history
14
+
pseudo-states, eventless transitions, `done.state` events — work
15
+
identically in both engines.
86
16
87
17
88
-
```{seealso}
89
-
See {ref}`sphx_glr_auto_examples_air_conditioner_machine.py` for an example of
90
-
async code with a state machine.
91
-
```
18
+
## Writing async callbacks
92
19
20
+
Declare any callback as `async def` and the engine handles the rest:
93
21
94
22
```py
95
23
>>>classAsyncStateMachine(StateChart):
96
-
... initial = State('Initial', initial=True)
97
-
... final = State('Final', final=True)
24
+
... initial = State("Initial", initial=True)
25
+
... final = State("Final", final=True)
98
26
...
99
27
... keep = initial.to.itself(internal=True)
100
28
... advance = initial.to(final)
@@ -114,13 +42,12 @@ Result is 42
114
42
115
43
```
116
44
117
-
## Sync codebase with async callbacks
118
-
119
-
The same state machine with async callbacks can be executed in a synchronous codebase,
120
-
even if the calling context don't have an asyncio loop.
121
-
122
-
If needed, the state machine will create a loop using `asyncio.new_event_loop()` and callbacks will be awaited using `loop.run_until_complete()`.
45
+
### Using from synchronous code
123
46
47
+
The same state machine can be used from a synchronous context — even
48
+
without a running `asyncio` loop. The engine creates one internally
49
+
with `asyncio.new_event_loop()` and awaits callbacks using
50
+
`loop.run_until_complete()`:
124
51
125
52
```py
126
53
>>> sm = AsyncStateMachine()
@@ -134,88 +61,63 @@ Result is 42
134
61
135
62
136
63
(initial state activation)=
137
-
## Initial State Activation for Async Code
138
64
65
+
## Initial state activation
139
66
140
-
If **on async code** you perform checks against the `configuration`, like a loop `while not sm.is_terminated:`, then you must manually
141
-
await for the [activate initial state](statemachine.StateChart.activate_initial_state) to be able to check the configuration.
142
-
143
-
```{hint}
144
-
This manual initial state activation on async is because Python don't allow awaiting at class initalization time and the initial state activation may contain async callbacks that must be awaited.
145
-
```
146
-
147
-
If you don't do any check for configuration externally, just ignore this as the initial state is activated automatically before the first event trigger is handled.
148
-
149
-
You get an error checking the configuration before the initial state activation:
67
+
In async code, Python cannot `await` during `__init__`, so the initial
68
+
state is **not** activated at instantiation time. If you inspect
69
+
`configuration` immediately after creating the instance, it won't reflect
0 commit comments