@@ -605,29 +605,77 @@ controlled. Documentation of this is in the code.
605
605
## 3.6 Task cancellation
606
606
607
607
` 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:
619
633
620
634
``` python
621
635
asyncio.cancel(my_coro)
622
636
await asyncio.sleep(0 ) # Ensure my_coro gets scheduled with the exception
623
637
# my_coro will be cancelled now
624
638
```
625
639
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
+ ```
627
674
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:
631
679
632
680
1 . ` Cancellable ` This allows one or more tasks to be assigned to a group. A
633
681
coro can cancel all tasks in the group, pausing until this has been achieved.
0 commit comments