@@ -1171,24 +1171,52 @@ asyncio.run(bar())
1171
1171
1172
1172
###### [ Contents] ( ./TUTORIAL.md#contents )
1173
1173
1174
- # 5 Exceptions timeouts and cancellation
1174
+ # 5. Exceptions timeouts and cancellation
1175
1175
1176
1176
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.
1179
1178
1180
1179
## 5.1 Exceptions
1181
1180
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 .
1188
1187
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.
1192
1220
1193
1221
There is a "gotcha" illustrated by this code sample. If allowed to run to
1194
1222
completion it works as expected.
@@ -1230,6 +1258,12 @@ transferred to the scheduler. Consequently applications requiring cleanup code
1230
1258
in response to a keyboard interrupt should trap the exception at the outermost
1231
1259
scope.
1232
1260
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
+
1233
1267
###### [ Contents] ( ./TUTORIAL.md#contents )
1234
1268
1235
1269
## 5.2 Cancellation and Timeouts
0 commit comments