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
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.
8
-
9
-
This is achieved through a new concept called **engine**, an internal strategy pattern abstraction that manages transitions and callbacks.
10
-
11
-
There are two engines, {ref}`SyncEngine` and {ref}`AsyncEngine`.
12
-
13
-
14
-
## Sync vs async engines
15
-
16
-
Engines are internal and are activated automatically by inspecting the registered callbacks in the following scenarios.
17
-
18
-
19
-
```{list-table} Sync vs async engines
20
-
:header-rows: 1
21
-
22
-
* - Outer scope
23
-
- Async callbacks?
24
-
- Engine
25
-
- Creates internal loop
26
-
- Reuses external loop
27
-
* - Sync
28
-
- No
29
-
- SyncEngine
30
-
- No
31
-
- No
32
-
* - Sync
33
-
- Yes
34
-
- AsyncEngine
35
-
- Yes
36
-
- No
37
-
* - Async
38
-
- No
39
-
- SyncEngine
40
-
- No
41
-
- No
42
-
* - Async
43
-
- Yes
44
-
- AsyncEngine
45
-
- No
46
-
- Yes
47
-
48
-
```
49
-
50
-
Outer scope
51
-
: The context in which the state machine **instance** is created.
52
-
53
-
Async callbacks?
54
-
: Indicates whether the state machine has declared asynchronous callbacks or conditions.
55
-
56
-
Engine
57
-
: The engine that will be utilized.
58
-
59
-
Creates internal loop
60
-
: 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).
61
-
62
-
Reuses external loop
63
-
: 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.
64
-
65
-
66
-
67
-
```{note}
68
-
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.
69
7
```
70
8
71
-
### SyncEngine
72
-
Activated if there are no async callbacks. All code runs exactly as it did before version 2.3.0.
73
-
There's no event loop.
74
-
75
-
### AsyncEngine
76
-
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.
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.
77
12
13
+
All statechart features — compound states, parallel states, history
14
+
pseudo-states, eventless transitions, `done.state` events — work
15
+
identically in both engines.
78
16
79
17
80
-
## Asynchronous Support
81
-
82
-
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.
83
-
84
-
85
-
```{seealso}
86
-
See {ref}`sphx_glr_auto_examples_air_conditioner_machine.py` for an example of
87
-
async code with a state machine.
88
-
```
18
+
## Writing async callbacks
89
19
20
+
Declare any callback as `async def` and the engine handles the rest:
90
21
91
22
```py
92
23
>>>classAsyncStateMachine(StateChart):
93
-
... initial = State('Initial', initial=True)
94
-
... final = State('Final', final=True)
24
+
... initial = State("Initial", initial=True)
25
+
... final = State("Final", final=True)
95
26
...
96
27
... keep = initial.to.itself(internal=True)
97
28
... advance = initial.to(final)
@@ -111,13 +42,12 @@ Result is 42
111
42
112
43
```
113
44
114
-
## Sync codebase with async callbacks
115
-
116
-
The same state machine with async callbacks can be executed in a synchronous codebase,
117
-
even if the calling context don't have an asyncio loop.
118
-
119
-
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
120
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()`:
121
51
122
52
```py
123
53
>>> sm = AsyncStateMachine()
@@ -131,88 +61,63 @@ Result is 42
131
61
132
62
133
63
(initial state activation)=
134
-
## Initial State Activation for Async Code
135
64
65
+
## Initial state activation
136
66
137
-
If **on async code** you perform checks against the `configuration`, like a loop `while not sm.is_terminated:`, then you must manually
138
-
await for the [activate initial state](statemachine.StateChart.activate_initial_state) to be able to check the configuration.
139
-
140
-
```{hint}
141
-
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.
142
-
```
143
-
144
-
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.
145
-
146
-
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