Skip to content

Commit fa38336

Browse files
committed
Improvements to docs.
1 parent 78bb133 commit fa38336

File tree

3 files changed

+77
-87
lines changed

3 files changed

+77
-87
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ This GitHub repository consists of the following parts:
2222
boards to communicate without using a UART. Primarily intended to enable a
2323
a Pyboard-like device to achieve bidirectional communication with an ESP8266.
2424
* [Under the hood](./UNDER_THE_HOOD.md) A guide to help understand the
25-
`uasyncio` code. Strictly for scheduler geeks...
25+
`uasyncio` code. For scheduler geeks and those wishing to modify `uasyncio`.
2626

2727
## 1.1 A new "priority" version.
2828

TUTORIAL.md

Lines changed: 40 additions & 86 deletions
Original file line numberDiff line numberDiff line change
@@ -139,19 +139,17 @@ and rebuilding.
139139

140140
6. [Hints and tips](./TUTORIAL.md#6-hints-and-tips)
141141

142-
6.1 [Coroutines are generators](./TUTORIAL.md#61-coroutines-are-generators)
142+
6.1 [Program hangs](./TUTORIAL.md#61-program-hangs)
143143

144-
6.2 [Program hangs](./TUTORIAL.md#62-program-hangs)
144+
6.2 [uasyncio retains state](./TUTORIAL.md#62-uasyncio-retains-state)
145145

146-
6.3 [uasyncio retains state](./TUTORIAL.md#63-uasyncio-retains-state)
146+
6.3 [Garbage Collection](./TUTORIAL.md#63-garbage-collection)
147147

148-
6.4 [Garbage Collection](./TUTORIAL.md#64-garbage-collection)
148+
6.4 [Testing](./TUTORIAL.md#64-testing)
149149

150-
6.5 [Testing](./TUTORIAL.md#65-testing)
150+
6.5 [A common error](./TUTORIAL.md#65-a-common-error) This can be hard to find.
151151

152-
6.6 [A common hard to find error](./TUTORIAL.md#66-a-common-error)
153-
154-
6.7 [Socket programming](./TUTORIAL.md#67-socket-programming)
152+
6.6 [Socket programming](./TUTORIAL.md#66-socket-programming)
155153

156154
7. [Notes for beginners](./TUTORIAL.md#7-notes-for-beginners)
157155

@@ -169,8 +167,6 @@ and rebuilding.
169167

170168
7.7 [Polling](./TUTORIAL.md#77-polling)
171169

172-
8. [Modifying uasyncio](./TUTORIAL.md#8-modifying-uasyncio)
173-
174170
# 1. Cooperative scheduling
175171

176172
The technique of cooperative multi-tasking is widely used in embedded systems.
@@ -1042,6 +1038,10 @@ Python's `select.poll` system: because the polling is done in C it is faster
10421038
and more efficient than explicit polling. The use of `stream I/O` is discussed
10431039
[here](./TUTORIAL.md#53-using-the-stream-mechanism).
10441040

1041+
Owing to its efficiency implicit polling benefits most fast I/O device drivers:
1042+
streaming drivers can be written for many devices not normally considered as
1043+
streaming devices [section 5.4](./TUTORIAL.md#54-writing-streaming-device-drivers).
1044+
10451045
###### [Contents](./TUTORIAL.md#contents)
10461046

10471047
## 5.1 Timing issues
@@ -1075,10 +1075,9 @@ and `sleep_ms()` functions. The worst-case value for this overrun may be
10751075
calculated by summing, for every other coro, the worst-case execution time
10761076
between yielding to the scheduler.
10771077

1078-
There is an experimental version of uasyncio presented [here](./FASTPOLL.md).
1079-
This provides for callbacks which run on every iteration of the scheduler
1080-
enabling a coro to wait on an event with much reduced latency. It is hoped
1081-
that improvements to `uasyncio` will remove the need for this in future.
1078+
The [fast_io](./FASTPOLL.md) version of `uasyncio` in this repo provides a way
1079+
to ensure that stream I/O is polled on every iteration of the scheduler. It is
1080+
hoped that official `uasyncio` will adopt code to this effect in due course.
10821081

10831082
###### [Contents](./TUTORIAL.md#contents)
10841083

@@ -1121,7 +1120,7 @@ class RecordOrientedUart():
11211120
self.uart = UART(4, 9600)
11221121
self.data = b''
11231122

1124-
def __await__(self):
1123+
def __iter__(self): # Not __await__ issue #2678
11251124
data = b''
11261125
while not data.endswith(self.DELIMITER):
11271126
yield from asyncio.sleep(0) # Neccessary because:
@@ -1130,8 +1129,6 @@ class RecordOrientedUart():
11301129
data = b''.join((data, self.uart.read(self.uart.any())))
11311130
self.data = data
11321131

1133-
__iter__ = __await__ # workround for issue #2678
1134-
11351132
async def send_record(self, data):
11361133
data = b''.join((data, self.DELIMITER))
11371134
self.uart.write(data)
@@ -1251,8 +1248,7 @@ data as is available.
12511248
`readline()` Return as many characters as are available up to and including any
12521249
newline character. Required if you intend to use `StreamReader.readline()`
12531250
`read(n)` Return as many characters as are available but no more than `n`.
1254-
Required if you plan to use `StreamReader.read()` or
1255-
`StreamReader.readexactly()`
1251+
Required to use `StreamReader.read()` or `StreamReader.readexactly()`
12561252

12571253
A writeable driver must provide this synchronous method:
12581254
`write` Args `buf`, `off`, `sz`. Arguments:
@@ -1332,8 +1328,8 @@ async def timer_test(n):
13321328
```
13331329

13341330
With official `uasyncio` this confers no benefit over `await asyncio.sleep_ms()`.
1335-
With the [priority version](./FASTPOLL.md) it offers much more precise delays
1336-
under a common usage scenario.
1331+
Using [fast_io](./FASTPOLL.md) it offers much more precise delays under the
1332+
common usage pattern where coros await a zero delay.
13371333

13381334
It is possible to use I/O scheduling to associate an event with a callback.
13391335
This is more efficient than a polling loop because the coro doing the polling
@@ -1385,20 +1381,19 @@ class PinCall(io.IOBase):
13851381
```
13861382

13871383
Once again with official `uasyncio` latency can be high. Depending on
1388-
application design the [priority version](./FASTPOLL.md) can greatly reduce
1384+
application design the [fast_io](./FASTPOLL.md) version can greatly reduce
13891385
this.
13901386

13911387
The demo program `iorw.py` illustrates a complete example. Note that, at the
13921388
time of writing there is a bug in `uasyncio` which prevents this from woking.
13931389
See [this GitHub thread](https://github.com/micropython/micropython/pull/3836#issuecomment-397317408).
13941390
There are two solutions. A workround is to write two separate drivers, one
1395-
read-only and the other write-only. Alternatively an experimental version
1396-
of `uasyncio` is [documented here](./FASTPOLL.md) which addresses this and
1397-
also enables the priority of I/O to be substantially raised.
1391+
read-only and the other write-only. Alternatively the
1392+
[fast_io](./FASTPOLL.md) addresses this.
13981393

1399-
In the official `uasyncio` is scheduled quite infrequently. See
1394+
In the official `uasyncio` I/O is scheduled quite infrequently. See
14001395
[see this GitHub RFC](https://github.com/micropython/micropython/issues/2664).
1401-
The experimental version addresses this issue.
1396+
The `fast_io` version addresses this issue.
14021397

14031398
###### [Contents](./TUTORIAL.md#contents)
14041399

@@ -1407,15 +1402,15 @@ The experimental version addresses this issue.
14071402
This may be found in the `nec_ir` directory. Its use is documented
14081403
[here](./nec_ir/README.md). The demo provides a complete device driver example:
14091404
a receiver/decoder for an infra red remote controller. The following notes are
1410-
salient points regarding its asyncio usage.
1405+
salient points regarding its `asyncio` usage.
14111406

14121407
A pin interrupt records the time of a state change (in us) and sets an event,
14131408
passing the time when the first state change occurred. A coro waits on the
14141409
event, yields for the duration of a data burst, then decodes the stored data
14151410
before calling a user-specified callback.
14161411

14171412
Passing the time to the `Event` instance enables the coro to compensate for
1418-
any asyncio latency when setting its delay period.
1413+
any `asyncio` latency when setting its delay period.
14191414

14201415
###### [Contents](./TUTORIAL.md#contents)
14211416

@@ -1433,26 +1428,6 @@ run while acquisition is in progress.
14331428

14341429
# 6 Hints and tips
14351430

1436-
## 6.1 Coroutines are generators
1437-
1438-
In MicroPython coroutines are generators. This is not the case in CPython.
1439-
Issuing `yield` in a coro will provoke a syntax error in CPython, whereas in
1440-
MicroPython it has the same effect as `await asyncio.sleep(0)`. The surest way
1441-
to write error free code is to use CPython conventions and assume that coros
1442-
are not generators.
1443-
1444-
The following will work. If you use them, be prepared to test your code against
1445-
each uasyncio release because the behaviour is not necessarily guaranteed.
1446-
1447-
```python
1448-
yield from coro # Equivalent to await coro: continue when coro terminates.
1449-
yield # Reschedule current coro in round-robin fashion.
1450-
yield 100 # Pause 100ms - equivalent to above
1451-
```
1452-
1453-
Issuing `yield` or `yield 100` is slightly faster than the equivalent `await`
1454-
statements.
1455-
14561431
###### [Contents](./TUTORIAL.md#contents)
14571432

14581433
## 6.1 Program hangs
@@ -1532,6 +1507,7 @@ the outer loop:
15321507

15331508
It is perhaps worth noting that this error would not have been apparent had
15341509
data been sent to the UART at a slow rate rather than via a loopback test.
1510+
Welcome to the joys of realtime programming.
15351511

15361512
###### [Contents](./TUTORIAL.md#contents)
15371513

@@ -1540,11 +1516,18 @@ data been sent to the UART at a slow rate rather than via a loopback test.
15401516
If a function or method is defined with `async def` and subsequently called as
15411517
if it were a regular (synchronous) callable, MicroPython does not issue an
15421518
error message. This is [by design](https://github.com/micropython/micropython/issues/3241).
1543-
It typically leads to a program silently failing to run correctly.
1519+
It typically leads to a program silently failing to run correctly:
1520+
1521+
```python
1522+
async def foo():
1523+
# code
1524+
loop.create_task(foo) # Case 1: foo will never run
1525+
foo() # Case 2: Likewise.
1526+
```
15441527

15451528
I have [a PR](https://github.com/micropython/micropython-lib/pull/292) which
1546-
proposes a fix for this. The [experimental fast_io](./FASTPOLL.md) version
1547-
implements this fix.
1529+
proposes a fix for case 1. The [fast_io](./FASTPOLL.md) version implements
1530+
this.
15481531

15491532
The script `check_async_code.py` attempts to locate instances of questionable
15501533
use of coros. It is intended to be run on a PC and uses Python3. It takes a
@@ -1569,11 +1552,14 @@ bar(foo) # These lines will warn but may or may not be correct
15691552
bar(foo())
15701553
z = (foo,)
15711554
z = (foo(),)
1555+
foo() # Will warn: is surely wrong.
15721556
```
15731557

15741558
I find it useful as-is but improvements are always welcome.
15751559

1576-
## 6.7 Socket programming
1560+
###### [Contents](./TUTORIAL.md#contents)
1561+
1562+
## 6.6 Socket programming
15771563

15781564
The use of nonblocking sockets requires some attention to detail. If a
15791565
nonblocking read is performed, because of server latency, there is no guarantee
@@ -1587,7 +1573,7 @@ practice a timeout is likely to be required to cope with server outages.
15871573
A further complication is that, at the time of writing, the ESP32 port has
15881574
issues which require rather unpleasant hacks for error-free operation.
15891575

1590-
The file `sock_nonblock.py` illustrates the sort of techniques required. It is
1576+
The file [sock_nonblock.py](./sock_nonblock.py) illustrates the sort of techniques required. It is
15911577
not a working demo, and solutions are likely to be application dependent.
15921578

15931579
An alternative approach is to use blocking sockets with `StreamReader` and
@@ -1884,35 +1870,3 @@ services the hardware and sets a flag. A coro polls the flag: if it's set it
18841870
handles the data and clears the flag. A better approach is to use an `Event`.
18851871

18861872
###### [Contents](./TUTORIAL.md#contents)
1887-
1888-
# 8 Modifying uasyncio
1889-
1890-
The library is designed to be extensible. By following these guidelines a
1891-
module can be constructed which alters the functionality of asyncio without the
1892-
need to change the official library. Such a module may be used where `uasyncio`
1893-
is implemented as frozen bytecode.
1894-
1895-
Assume that the aim is to alter the event loop. The module should issue
1896-
1897-
```python
1898-
from uasyncio import *
1899-
```
1900-
1901-
The event loop should be subclassed from `PollEventLoop` (defined in
1902-
`__init__.py`).
1903-
1904-
The event loop is instantiated by the first call to `get_event_loop()`: this
1905-
creates a singleton instance. This is returned by every call to
1906-
`get_event_loop()`. On the assumption that the constructor arguments for the
1907-
new class differ from those of the base class, the module will need to redefine
1908-
`get_event_loop()` along the following lines:
1909-
1910-
```python
1911-
_event_loop = None # The singleton instance
1912-
_event_loop_class = MyNewEventLoopClass # The class, not an instance
1913-
def get_event_loop(args):
1914-
global _event_loop
1915-
if _event_loop is None:
1916-
_event_loop = _event_loop_class(args) # Instantiate once only
1917-
return _event_loop
1918-
```

UNDER_THE_HOOD.md

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,8 @@ fast_io/__init__.py
3030
fast_io/core.py
3131
```
3232

33+
###### [Main README](./README.md)
34+
3335
# Generators and coroutines
3436

3537
In MicroPython coroutines and generators are identical: this differs from
@@ -250,3 +252,37 @@ retrieved from `.rdobjmap` and queued for scheduling. This is done via
250252
I/O queue has been instantiated.
251253

252254
Writing is handled similarly.
255+
256+
# Modifying uasyncio
257+
258+
The library is designed to be extensible. By following these guidelines a
259+
module can be constructed which alters the functionality of asyncio without the
260+
need to change the official library. Such a module may be used where `uasyncio`
261+
is implemented as frozen bytecode as in official release binaries.
262+
263+
Assume that the aim is to alter the event loop. The module should issue
264+
265+
```python
266+
from uasyncio import *
267+
```
268+
269+
The event loop should be subclassed from `PollEventLoop` (defined in
270+
`__init__.py`).
271+
272+
The event loop is instantiated by the first call to `get_event_loop()`: this
273+
creates a singleton instance. This is returned by every call to
274+
`get_event_loop()`. On the assumption that the constructor arguments for the
275+
new class differ from those of the base class, the module will need to redefine
276+
`get_event_loop()` along the following lines:
277+
278+
```python
279+
_event_loop = None # The singleton instance
280+
_event_loop_class = MyNewEventLoopClass # The class, not an instance
281+
def get_event_loop(args):
282+
global _event_loop
283+
if _event_loop is None:
284+
_event_loop = _event_loop_class(args) # Instantiate once only
285+
return _event_loop
286+
```
287+
288+
###### [Main README](./README.md)

0 commit comments

Comments
 (0)