@@ -51,6 +51,8 @@ MicroPython firmware or one built from source.
51
51
52
52
4.3.2 [ Custom cleanup] ( ./PRIMITIVES.md#432-custom-cleanup )
53
53
54
+ 4.3.3 [ Changes] ( ./PRIMITIVES.md#433-changes )
55
+
54
56
## 1.1 Synchronisation Primitives
55
57
56
58
There is often a need to provide synchronisation between coros. A common
@@ -314,28 +316,29 @@ is raised.
314
316
315
317
# 4. Task Cancellation
316
318
317
- Note to users of releases prior to 31st Dec 2017: this API has changed. It
318
- should now be stable .
319
+ This has been under active development. Existing users please see
320
+ [ Changes ] ( ./PRIMITIVES.md#433-changes ) for recent API changes .
319
321
320
- In ` uasyncio ` task cancellation is achieved by throwing an exception to the
321
- coro to be cancelled in a special way: cancellation is deferred until the coro
322
+ ` uasyncio ` now provides a ` cancel(coro) ` function. This works by throwing an
323
+ exception to the coro in a special way: cancellation is deferred until the coro
322
324
is next scheduled. This mechanism works with nested coros. However there is a
323
325
limitation. If a coro issues ` await uasyncio.sleep(secs) ` or
324
326
` uasyncio.sleep_ms(ms) ` scheduling will not occur until the time has elapsed.
325
327
This introduces latency into cancellation which matters in some use-cases.
328
+ Crucially there is no inbuilt mechanism for verifying when cancellation has
329
+ actually occurred. This library provides solutions.
326
330
327
331
Cancellation is supported by two classes, ` Cancellable ` and ` NamedTask ` . The
328
332
` Cancellable ` class allows the creation of named groups of anonymous tasks
329
- which may be cancelled as a group. Crucially this awaits actual completion of
330
- cancellation of all tasks in the group.
333
+ which may be cancelled as a group. This awaits completion of cancellation of
334
+ all tasks in the group.
331
335
332
336
The ` NamedTask ` class enables a task to be associated with a user supplied
333
- name, enabling it to be cancelled and its status checked. Cancellation does not
334
- await confirmation of completion. This may be achieved by means of a ` Barrier `
335
- instance although the normal approach is to use a ` Cancellable ` task.
337
+ name, enabling it to be cancelled and its status checked. Cancellation
338
+ optionally awaits confirmation of completion.
336
339
337
340
For cases where cancellation latency is of concern ` asyn.py ` offers a ` sleep `
338
- function which can reduce this .
341
+ function which provides a delay which reduces latency .
339
342
340
343
## 4.1 Coro sleep
341
344
@@ -404,22 +407,26 @@ Constructor mandatory args:
404
407
Constructor optional positional args:
405
408
* Any further positional args are passed to the coro.
406
409
407
- Constructor optional keyword arg:
410
+ Constructor optional keyword arg:
408
411
* ` group ` Integer or string. Default 0. See Groups below.
409
412
410
- Class methods:
411
- * ` cancel_all ` Asynchronous. Optional arg ` group ` default 0.
412
- Cancel all instances in the specified group and await completion. See Groups
413
- below.
413
+ Class public methods:
414
+ * ` cancel_all ` Asynchronous. In practice this is the only method required by
415
+ user code.
416
+ Optional args ` group ` default 0, ` nowait ` default ` False ` .
417
+ The ` nowait ` arg is for use by the ` NamedTask ` derived class. The default
418
+ value is assumed below.
419
+ The method cancels all instances in the specified group and awaits completion.
420
+ See Groups below.
414
421
The ` cancel_all ` method will complete when all ` Cancellable ` instances have
415
422
been cancelled or terminated naturally before ` cancel_all ` was launched.
416
- Each coro will receive a ` CancelError ` exception when it is next scheduled.
417
- The coro should trap this, await the ` stopped ` method and quit. If the coro
418
- quits for any reason it should call the ` end ` method. The ` @cancellable `
419
- decorator handles the above housekeeping .
423
+ Each coro will receive a ` StopTask ` exception when it is next scheduled. If
424
+ the coro is written using the ` @cancellable ` decorator this is handled
425
+ automatically.
426
+ It is possible to trap the ` StopTask ` exception: see 'Custom cleanup' below .
420
427
* ` end ` Synchronous. Arg: The coro task number. Informs the class that a
421
428
` Cancellable ` instance has ended, either normally or by cancellation.
422
- * ` stopped ` Asynchronous . Arg: The coro task number. Informs the class that a
429
+ * ` stopped ` Synchronous . Arg: The coro task number. Informs the class that a
423
430
Cancellable instance has been cancelled.
424
431
425
432
Bound method:
@@ -459,9 +466,10 @@ async def bar(task_id):
459
466
try :
460
467
await sleep(1 )
461
468
except StopTask:
462
- await Cancellable.stopped(task_no)
469
+ Cancellable.stopped(task_no)
463
470
return False
464
471
else :
472
+ Cancellable.stopped(task_no)
465
473
return True
466
474
finally :
467
475
Cancellable.end(task_no)
@@ -473,14 +481,16 @@ async def bar(task_id):
473
481
474
482
A ` NamedTask ` instance is associated with a user-defined name such that the
475
483
name may outlive the task: a coro may end but the class enables its state to be
476
- checked.
484
+ checked. It is a subclass of ` Cancellable ` and ts constructor disallows
485
+ duplicate names: each instance of a coro must be assigned a unique name.
477
486
478
- A ` NamedTask ` task is defined with the ` @namedtask ` decorator. When scheduled it
479
- will receive an initial arg which is the name followed by any user-defined args.
487
+ A ` NamedTask ` coro is normally defined with the ` @cancellable ` decorator. When
488
+ scheduled it will receive an initial arg which is a ` TaskId ` instance followed
489
+ by any user-defined args. Normally the ` task_id ` can be ignored.
480
490
481
491
``` python
482
- @namedtask
483
- async def foo (name , arg1 , arg2 ):
492
+ @cancellable
493
+ async def foo (task_id , arg1 , arg2 ):
484
494
await asyn.sleep(1 )
485
495
print (' Task foo has ended.' , arg1, arg2)
486
496
```
@@ -494,10 +504,10 @@ loop = asyncio.get_event_loop() # Or schedule and continue:
494
504
loop.create_task(NamedTask(' my nums' , foo, 10 , 11 )()) # Note () syntax.
495
505
```
496
506
497
- Cancellation is performed with
507
+ Cancellation is performed with:
498
508
499
509
``` python
500
- NamedTask.cancel(' my foo' )
510
+ await NamedTask.cancel(' my foo' ) # API change
501
511
```
502
512
503
513
When cancelling a task there is no need to check if the task is still running:
@@ -518,17 +528,20 @@ Mandatory args:
518
528
wait for confirmation of successful cancellation.
519
529
520
530
Class methods:
521
- * ` cancel ` Synchronous. Arg: a coro name.
531
+ * ` cancel ` Asynchronous. ** [ API change: was synchronous] **
532
+ Mandatory arg: a coro name.
533
+ Optional boolean arg ` nowait ` default ` True `
534
+ By default it will return soon. If ` nowait ` is ` False ` it will pause until the
535
+ coro has completed cancellation.
522
536
The named coro will receive a ` CancelError ` exception the next time it is
523
- scheduled. The coro should trap this, ensure the ` end ` bound coro is launched
524
- and return. The ` @namedtask ` decorator handles this, ensuring ` end ` is called
525
- under all circumstances.
537
+ scheduled. The coro should trap this, call the ` end ` method and return. The
538
+ ` @namedtask ` decorator handles this, ensuring ` end ` is called under all
539
+ circumstances.
526
540
` cancel ` will return ` True ` if the coro was cancelled. It will return ` False `
527
541
if the coro has already ended or been cancelled.
528
542
* ` is_running ` Synchronous. Arg: A coro name. Returns ` True ` if coro is queued
529
- for scheduling, ` False ` if it has ended or been scheduled for cancellation.
530
- See note in 4.3.1 below.
531
- * ` end ` Asynchronous. Arg: A coro name. Run by the ` NamedTask ` instance to
543
+ for scheduling, ` False ` if it has ended or been cancelled.
544
+ * ` end ` Synchronous. Arg: A coro name. Run by the ` NamedTask ` instance to
532
545
inform the class that the instance has ended. Completes quickly.
533
546
534
547
Bound method:
@@ -537,43 +550,55 @@ Bound method:
537
550
538
551
### 4.3.1 Latency and Barrier objects
539
552
540
- Consider the latency discussed at the start of section 3.6: cancellation raises
541
- an exception which will be handled when the coro is next scheduled. There is no
542
- mechanism to determine if a cancelled task has been scheduled and has acted on
543
- the ` StopTask ` exception. Consequently calling ` is_running() ` on a recently
544
- cancelled task may return ` False ` even though ` uasyncio ` will run the task for
545
- one final time.
546
-
547
- Confirmation of cancellation may be achieved by means of a ` Barrier ` object,
548
- however practical use-cases for this are few - if confirmation is required the
549
- normal approach is to use ` Cancellable ` tasks, if necessary in groups having a
550
- single member. However the approach is described below.
553
+ It is possible to get confirmation of cancellation of an arbitrary set of
554
+ ` NamedTask ` instances by instantiating a ` Barrier ` and passing it to the
555
+ constructor of each member. Practical use-cases for this are few - the normal
556
+ approach is to use a group of ` Cancellable ` tasks. The approach is described
557
+ below.
551
558
552
559
If a ` Barrier ` instance is passed to the ` NamedTask ` constructor, a task
553
560
performing cancellation can pause until a set of cancelled tasks have
554
561
terminated. The ` Barrier ` is constructed with the number of dependent tasks
555
562
plus one (the task which is to wait on it). It is passed to the constructor of
556
563
each dependent task and the cancelling task waits on it after cancelling all
557
- dependent tasks. Note that the tasks being cancelled terminate immediately.
564
+ dependent tasks. Each task being cancelled terminates 'immediately' subject
565
+ to latency.
558
566
559
567
See examples in ` cantest.py ` e.g. ` cancel_test2() ` .
560
568
561
569
### 4.3.2 Custom cleanup
562
570
563
- A task created with the ` @namedtask ` decorator can intercept the ` StopTask `
564
- exception if necessary. This might be done for cleanup or to return a
565
- 'cancelled' status.
571
+ A coroutine to be used as a ` NamedTask ` can intercept the ` StopTask ` exception
572
+ if necessary. This might be done for cleanup or to return a 'cancelled' status.
573
+ To do this, do not use the ` @cancellable ` decorator. The coro should have the
574
+ following form:
566
575
567
576
``` python
568
- @namedtask
569
- async def foo (_ ):
577
+ async def foo (task_id ):
570
578
try :
571
579
await asyncio.sleep(1 ) # User code here
580
+ NamedTask.stopped(task_id) # Inform class that it has stopped
572
581
return True
573
582
except StopTask:
574
583
return False
584
+ finally :
585
+ # Inform class that it has stopped or been cancelled
586
+ NamedTask.end(task_id)
575
587
```
576
588
589
+ ### 4.3.3 Changes
590
+
591
+ The ` NamedTask ` class has been rewritten as a subclass of ` Cancellable ` . This
592
+ is to simplify the code and to ensure accuracy of the ` is_running ` method. The
593
+ latest API changes are:
594
+ * ` Cancellable.stopped() ` is now synchronous.
595
+ * ` NamedTask.cancel() ` is now asynchronous.
596
+ * ` NamedTask ` coros now receive a ` TaskId ` instance as their 1st arg.
597
+ * The ` @namedtask ` works but is now an alias for ` @cancellable ` .
598
+
599
+ The drive to simplify code comes from the fact that ` uasyncio ` is itself under
600
+ development. Tracking changes is an inevitable headache.
601
+
577
602
###### [ Contents] ( ./PRIMITIVES.md#contents )
578
603
579
604
#### ExitGate (obsolete)
0 commit comments