Skip to content

Commit c3f859a

Browse files
committed
Tutorial: improve task cancellation section.
1 parent 190a0f9 commit c3f859a

File tree

1 file changed

+63
-15
lines changed

1 file changed

+63
-15
lines changed

TUTORIAL.md

Lines changed: 63 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -605,29 +605,77 @@ controlled. Documentation of this is in the code.
605605
## 3.6 Task cancellation
606606

607607
`uasyncio` provides a `cancel(coro)` function. This works by throwing an
608-
exception to the coro in a special way: cancellation is deferred until the coro
609-
is next scheduled. This mechanism works with nested coros. However there is a
610-
limitation. If a coro issues `await uasyncio.sleep(secs)` or
611-
`uasyncio.sleep_ms(ms)` scheduling will not occur until the time has elapsed.
612-
This introduces latency into cancellation which matters in some use-cases.
613-
Another source of latency is where a task is waiting on I/O. In many
614-
applications it is necessary for the task performing cancellation to pause
615-
until all cancelled coros have actually stopped.
616-
617-
If the task to be cancelled only pauses on zero delays and never waits on I/O,
618-
the round-robin nature of the scheduler avoids the need to verify cancellation:
608+
exception to the coro in a special way: when the coro is next scheduled it
609+
receives the exception. This mechanism works with nested coros. Usage is as
610+
follows:
611+
```python
612+
async def foo():
613+
while True:
614+
# do something every 10 secs
615+
await asyncio.sleep(10)
616+
617+
async def bar(loop):
618+
foo_instance = foo() # Create a coroutine instance
619+
loop.create_task(foo_instance)
620+
# code omitted
621+
asyncio.cancel(foo_instance)
622+
```
623+
In this example when `bar` issues `cancel` it will not take effect until `foo`
624+
is next scheduled. There is thus a latency of up to 10s in the cancellation of
625+
`foo`. Another source of latency would arise if `foo` waited on I/O. Where
626+
latency arises, `bar` cannot determine whether `foo` has yet been cancelled.
627+
This matters in some use-cases.
628+
629+
In many applications it is necessary for the task performing cancellation to
630+
pause until all cancelled coros have actually stopped. If the task to be
631+
cancelled only pauses on zero delays and never waits on I/O, the round-robin
632+
nature of the scheduler avoids the need to verify cancellation:
619633

620634
```python
621635
asyncio.cancel(my_coro)
622636
await asyncio.sleep(0) # Ensure my_coro gets scheduled with the exception
623637
# my_coro will be cancelled now
624638
```
625639
This does require that all coros awaited by `my_coro` also meet the zero delay
626-
criterion.
640+
criterion. For the general case where latency exists, solutions are discussed
641+
below.
642+
643+
Behaviour which may surprise the unwary arises when a coro to be cancelled is
644+
awaited rather than being launched by `create_task`. Consider this fragment:
645+
646+
```python
647+
async def foo():
648+
while True:
649+
# do something every 10 secs
650+
await asyncio.sleep(10)
651+
652+
async def foo_runner(foo_instance):
653+
await foo_instance
654+
print('This will not be printed')
655+
656+
async def bar(loop):
657+
foo_instance = foo()
658+
loop.create_task(foo_runner(foo_instance))
659+
# code omitted
660+
asyncio.cancel(foo_instance)
661+
```
662+
When `cancel` is called and `foo` is next scheduled it is removed from the
663+
scheduler's queue; because it lacks a `return` statement the calling routine
664+
`foo_runner` never resumes. The solution is to trap the exception:
665+
```python
666+
async def foo():
667+
try:
668+
while True:
669+
# do something every 10 secs
670+
await asyncio.sleep(10)
671+
except asyncio.CancelledError:
672+
return
673+
```
627674

628-
That special case notwithstanding, `uasyncio` lacks a mechanism for verifying
629-
when cancellation has actually occurred. The `asyn` library provides
630-
verification via the following classes:
675+
In general `uasyncio` lacks a mechanism for verifying when cancellation has
676+
actually occurred. Ad-hoc mechanisms based on trapping `CancelledError` may be
677+
devised. For convenience the `asyn` library provides means of awaiting the
678+
cancellation of one or more coros via these classes:
631679

632680
1. `Cancellable` This allows one or more tasks to be assigned to a group. A
633681
coro can cancel all tasks in the group, pausing until this has been achieved.

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy