Skip to content

Commit e1e8c2b

Browse files
committed
v3/TUTORIAL.md Fix error in section 5.1.
1 parent 2f79789 commit e1e8c2b

File tree

1 file changed

+46
-12
lines changed

1 file changed

+46
-12
lines changed

v3/TUTORIAL.md

Lines changed: 46 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1171,24 +1171,52 @@ asyncio.run(bar())
11711171

11721172
###### [Contents](./TUTORIAL.md#contents)
11731173

1174-
# 5 Exceptions timeouts and cancellation
1174+
# 5. Exceptions timeouts and cancellation
11751175

11761176
These topics are related: `uasyncio` enables the cancellation of tasks, and the
1177-
application of a timeout to a task, by throwing an exception to the task in a
1178-
special way.
1177+
application of a timeout to a task, by throwing an exception to the task.
11791178

11801179
## 5.1 Exceptions
11811180

1182-
Where an exception occurs in a task, it should be trapped either in that task
1183-
or in a task which is awaiting its completion. This ensures that the exception
1184-
is not propagated to the scheduler. If this occurred the scheduler would stop
1185-
running, passing the exception to the code which started the scheduler.
1186-
Consequently, to avoid stopping the scheduler, tasks launched with
1187-
`asyncio.create_task()` must trap any exceptions internally.
1181+
Consider a task `foo` created with `asyncio.create_task(foo())`. This task
1182+
might `await` other tasks, with potential nesting. If an exception occurs, it
1183+
will propagate up the chain until it reaches `foo`. This behaviour is as per
1184+
function calls: the exception propagates up the call chain until trapped. If
1185+
the exception is not trapped, the `foo` task stops with a traceback. Crucially
1186+
other tasks continue to run.
11881187

1189-
Using `throw` or `close` to throw an exception to a task is unwise. It subverts
1190-
`uasyncio` by forcing the task to run, and possibly terminate, when it is still
1191-
queued for execution.
1188+
This does not apply to the main task started with `asyncio.run`. If an
1189+
exception propagates to that task, the scheduler will stop. This can be
1190+
demonstrated as follows:
1191+
1192+
```python
1193+
import uasyncio as asyncio
1194+
1195+
async def bar():
1196+
await asyncio.sleep(0)
1197+
1/0
1198+
1199+
async def foo():
1200+
await asyncio.sleep(0)
1201+
print('Running bar')
1202+
await bar()
1203+
print('Does not print') # Because bar() raised an exception
1204+
1205+
async def main():
1206+
asyncio.create_task(foo())
1207+
for _ in range(5):
1208+
print('Working') # Carries on after the exception
1209+
await asyncio.sleep(0.5)
1210+
1/0 # Stops the scheduler
1211+
await asyncio.sleep(0)
1212+
print('This never happens')
1213+
await asyncio.sleep(0)
1214+
1215+
asyncio.run(main())
1216+
```
1217+
If `main` issued `await foo()` rather than `create_task(foo())` the exception
1218+
would propagate to `main`. Being untrapped, the scheduler and hence the script
1219+
would stop.
11921220

11931221
There is a "gotcha" illustrated by this code sample. If allowed to run to
11941222
completion it works as expected.
@@ -1230,6 +1258,12 @@ transferred to the scheduler. Consequently applications requiring cleanup code
12301258
in response to a keyboard interrupt should trap the exception at the outermost
12311259
scope.
12321260

1261+
#### Warning
1262+
1263+
Using `throw` or `close` to throw an exception to a coro is unwise. It subverts
1264+
`uasyncio` by forcing the coro to run, and possibly terminate, when it is still
1265+
queued for execution.
1266+
12331267
###### [Contents](./TUTORIAL.md#contents)
12341268

12351269
## 5.2 Cancellation and Timeouts

0 commit comments

Comments
 (0)