@@ -37,7 +37,6 @@ import uasyncio as asyncio
|
37 | 37 | 3.2.1 [Wait on multiple events](./TUTORIAL.md#321-wait-on-multiple-events) Pause until 1 of N events is set.
|
38 | 38 | 3.3 [Coordinating multiple tasks](./TUTORIAL.md#33-coordinating-multiple-tasks)
|
39 | 39 | 3.3.1 [gather](./TUTORIAL.md#331-gather)
|
40 |
| - 3.3.2 [TaskGroups](./TUTORIAL.md#332-taskgroups) Not yet in official build. |
41 | 40 | 3.4 [Semaphore](./TUTORIAL.md#34-semaphore)
|
42 | 41 | 3.4.1 [BoundedSemaphore](./TUTORIAL.md#341-boundedsemaphore)
|
43 | 42 | 3.5 [Queue](./TUTORIAL.md#35-queue)
|
@@ -134,6 +133,10 @@ mip.install(":peterhinch/micropython-async/v3/threadsafe")
|
134 | 133 | ```
|
135 | 134 | For non-networked targets use `mpremote` as described in
|
136 | 135 | [the official docs](http://docs.micropython.org/en/latest/reference/packages.html#installing-packages-with-mpremote).
|
| 136 | +```bash |
| 137 | +$ mpremote mip install :peterhinch/micropython-async/v3/primitives |
| 138 | +$ mpremote mip install :peterhinch/micropython-async/v3/threadsafe |
| 139 | +``` |
137 | 140 |
|
138 | 141 | ###### [Main README](../README.md)
|
139 | 142 |
|
@@ -276,7 +279,7 @@ line `main.py` and runs forever.
|
276 | 279 |
|
277 | 280 | ## 2.2 Coroutines and Tasks
|
278 | 281 |
|
279 |
| -The fundmental building block of `asyncio` is a coro. This is defined with |
| 282 | +The fundamental building block of `asyncio` is a coro. This is defined with |
280 | 283 | `async def` and usually contains at least one `await` statement. This minimal
|
281 | 284 | example waits 1 second before printing a message:
|
282 | 285 |
|
@@ -285,12 +288,16 @@ async def bar():
|
285 | 288 | await asyncio.sleep(1)
|
286 | 289 | print('Done')
|
287 | 290 | ```
|
288 |
| - |
289 |
| -V3 `asyncio` introduced the concept of a `Task`. A `Task` instance is created |
290 |
| -from a coro by means of the `create_task` method, which causes the coro to be |
291 |
| -scheduled for execution and returns a `Task` instance. In many cases, coros and |
292 |
| -tasks are interchangeable: the official docs refer to them as `awaitable`, for |
293 |
| -the reason that either of them may be the target of an `await`. Consider this: |
| 291 | +Just as a function does nothing until called, a coro does nothing until awaited |
| 292 | +or converted to a `Task`. The `create_task` method takes a coro as its argument |
| 293 | +and returns a `Task` instance, which is scheduled for execution. In |
| 294 | +```python |
| 295 | +async def foo(): |
| 296 | +await coro |
| 297 | +``` |
| 298 | +`coro` is run with `await` pausing until `coro` has completed. Sometimes coros |
| 299 | +and tasks are interchangeable: the CPython docs refer to them as `awaitable`, |
| 300 | +because either may be the target of an `await`. Consider this: |
294 | 301 |
|
295 | 302 | ```python
|
296 | 303 | import asyncio
|
@@ -856,79 +863,6 @@ async def main():
|
856 | 863 |
|
857 | 864 | asyncio.run(main())
|
858 | 865 | ```
|
859 |
| -### 3.3.2 TaskGroups |
860 |
| - |
861 |
| -The `TaskGroup` class is unofficially provided by |
862 |
| -[this PR](https://.com/micropython/micropython/pull/8791). It is well |
863 |
| -suited to applications where one or more of a group of tasks is subject to |
864 |
| -runtime exceptions. A `TaskGroup` is instantiated in an asynchronous context |
865 |
| -manager. The `TaskGroup` instantiates member tasks. When all have run to |
866 |
| -completion, the context manager terminates. Where `gather` is static, a task |
867 |
| -group can be dynamic: a task in a group may spawn further group members. Return |
868 |
| -values from member tasks cannot be retrieved. Results should be passed in other |
869 |
| -ways such as via bound variables, queues etc. |
870 |
| - |
871 |
| -An exception in a member task not trapped by that task is propagated to the |
872 |
| -task that created the `TaskGroup`. All tasks in the `TaskGroup` then terminate |
873 |
| -in an orderly fashion: cleanup code in any `finally` clause will run. When all |
874 |
| -cleanup code has completed, the context manager completes, and execution passes |
875 |
| -to an exception handler in an outer scope. |
876 |
| - |
877 |
| -If a member task is cancelled in code, that task terminates in an orderly way |
878 |
| -but the other members continue to run. |
879 |
| - |
880 |
| -The following illustrates the basic salient points of using a `TaskGroup`: |
881 |
| -```python |
882 |
| -import asyncio |
883 |
| -async def foo(n): |
884 |
| -for x in range(10 + n): |
885 |
| -print(f"Task {n} running.") |
886 |
| -await asyncio.sleep(1 + n/10) |
887 |
| -print(f"Task {n} done") |
888 |
| - |
889 |
| -async def main(): |
890 |
| -async with asyncio.TaskGroup() as tg: # Context manager pauses until members terminate |
891 |
| -for n in range(4): |
892 |
| -tg.create_task(foo(n)) # tg.create_task() creates a member task |
893 |
| -print("TaskGroup done") # All tasks have terminated |
894 |
| - |
895 |
| -asyncio.run(main()) |
896 |
| -``` |
897 |
| -This more complete example illustrates an exception which is not trapped by the |
898 |
| -member task. Cleanup code on all members runs when the exception occurs, |
899 |
| -followed by exception handling code in `main()`. |
900 |
| -```python |
901 |
| -import asyncio |
902 |
| -fail = True # Set False to demo normal completion |
903 |
| -async def foo(n): |
904 |
| -print(f"Task {n} running...") |
905 |
| -try: |
906 |
| -for x in range(10 + n): |
907 |
| -await asyncio.sleep(1 + n/10) |
908 |
| -if n==0 and x==5 and fail: |
909 |
| -raise OSError("Uncaught exception in task.") |
910 |
| -print(f"Task {n} done") |
911 |
| -finally: |
912 |
| -print(f"Task {n} cleanup") |
913 |
| - |
914 |
| -async def main(): |
915 |
| -try: |
916 |
| -async with asyncio.TaskGroup() as tg: |
917 |
| -for n in range(4): |
918 |
| -tg.create_task(foo(n)) |
919 |
| -print("TaskGroup done") # Does not get here if a task throws exception |
920 |
| -except Exception as e: |
921 |
| -print(f'TaskGroup caught exception: "{e}"') |
922 |
| -finally: |
923 |
| -print("TaskGroup finally") |
924 |
| - |
925 |
| -asyncio.run(main()) |
926 |
| -``` |
927 |
| -[This doc](https://vorpus.org/blog/notes-on-structured-concurrency-or-go-statement-considered-harmful/) |
928 |
| -provides background on the theory behind task groups and how they can improve |
929 |
| -program structure and reliablity. |
930 |
| - |
931 |
| -###### [Contents](./TUTORIAL.md#contents) |
932 | 866 |
|
933 | 867 | ## 3.4 Semaphore
|
934 | 868 |
|
@@ -2061,7 +1995,7 @@ asyncio.run(main())
|
2061 | 1995 | ```
|
2062 | 1996 | The `.readline` method will pause until `\n` is received.
|
2063 | 1997 |
|
2064 |
| -###### StreamWriter write methods |
| 1998 | +##### StreamWriter write methods |
2065 | 1999 |
|
2066 | 2000 | Writing to a `StreamWriter` occurs in two stages. The synchronous `.write`
|
2067 | 2001 | method concatenates data for later transmission. The asynchronous `.drain`
|
@@ -2078,7 +2012,7 @@ following methods: `ioctl`, `read`, `readline` and `write`. See
|
2078 | 2012 | [Writing device drivers](./TUTORIAL.md#64-writing--device-drivers)
|
2079 | 2013 | for details on how such drivers may be written in Python.
|
2080 | 2014 |
|
2081 |
| -###### StreamReader read methods |
| 2015 | +##### StreamReader read methods |
2082 | 2016 |
|
2083 | 2017 | The `StreamReader` read methods fall into two categories depending on whether
|
2084 | 2018 | they wait for a specific end condition. Thus `.readline` pauses until a newline
|
|
0 commit comments