27
27
3 . [ Synchronisation] ( ./TUTORIAL.md#3-synchronisation )
28
28
3.1 [ Lock] ( ./TUTORIAL.md#31-lock )
29
29
3.2 [ Event] ( ./TUTORIAL.md#32-event )
30
- 3.3 [ gather] ( ./TUTORIAL.md#33-gather )
30
+ 3.3 [ Coordinating multiple tasks] ( ./TUTORIAL.md#33-coordinating-multiple-tasks )
31
+   ;  ;  ;  ;  ; 3.3.1 [ gather] ( ./TUTORIAL.md#331-gather )
32
+   ;  ;  ;  ;  ; 3.3.2 [ TaskGroups] ( ./TUTORIAL.md#332-taskgroups )
31
33
3.4 [ Semaphore] ( ./TUTORIAL.md#34-semaphore )
32
34
  ;  ;  ;  ;  ; 3.4.1 [ BoundedSemaphore] ( ./TUTORIAL.md#341-boundedsemaphore )
33
35
3.5 [ Queue] ( ./TUTORIAL.md#35-queue )
@@ -701,7 +703,15 @@ constant creation of tasks. Arguably the `Barrier` class is the best approach.
701
703
702
704
###### [ Contents] ( ./TUTORIAL.md#contents )
703
705
704
- ## 3.3 gather
706
+ ## 3.3 Coordinating multiple tasks
707
+
708
+ Several tasks may be launched together with the launching task pausing until
709
+ all have completed. The ` gather ` mechanism is supported by CPython and
710
+ MicroPython. CPython 3.11 adds a ` TaskGroup ` class which is particularly
711
+ suited to applications where runtime exceptions may be encountered. It is not
712
+ yet officially supported by MicroPython.
713
+
714
+ ### 3.3.1 gather
705
715
706
716
This official ` uasyncio ` asynchronous method causes a number of tasks to run,
707
717
pausing until all have either run to completion or been terminated by
@@ -714,7 +724,7 @@ res = await asyncio.gather(*tasks, return_exceptions=True)
714
724
The keyword-only boolean arg ` return_exceptions ` determines the behaviour in
715
725
the event of a cancellation or timeout of tasks. If ` False ` the ` gather `
716
726
terminates immediately, raising the relevant exception which should be trapped
717
- by the caller. If ` True ` the ` gather ` continues to block until all have either
727
+ by the caller. If ` True ` the ` gather ` continues to pause until all have either
718
728
run to completion or been terminated by cancellation or timeout. In this case
719
729
tasks which have been terminated will return the exception object in the list
720
730
of return values.
@@ -767,6 +777,73 @@ async def main():
767
777
print (' Cancelled' )
768
778
print (' Result: ' , res)
769
779
780
+ asyncio.run(main())
781
+ ```
782
+ ### 3.3.2 TaskGroups
783
+
784
+ The ` TaskGroup ` class is unofficially provided by
785
+ [ this PR] ( https://github.com/micropython/micropython/pull/8791 ) . It is well
786
+ suited to applications where one or more of a group of tasks is subject to
787
+ runtime exceptions. A ` TaskGroup ` is instantiated in an asynchronous context
788
+ manager. The ` TaskGroup ` instantiates member tasks. When all have run to
789
+ completion the context manager terminates. Return values from member tasks
790
+ cannot be retrieved. Results should be passed in other ways such as via bound
791
+ variables, queues etc.
792
+
793
+ An exception in a member task not trapped by that task is propagated to the
794
+ task that created the ` TaskGroup ` . All tasks in the ` TaskGroup ` then terminate
795
+ in an orderly fashion: cleanup code in any ` finally ` clause will run. When all
796
+ cleanup code has completed, the context manager completes, and execution passes
797
+ to an exception handler in an outer scope.
798
+
799
+ If a member task is cancelled in code, that task terminates in an orderly way
800
+ but the other members continue to run.
801
+
802
+ The following illustrates the basic salient points of using a ` TaskGroup ` :
803
+ ``` python
804
+ import uasyncio as asyncio
805
+ async def foo (n ):
806
+ for x in range (10 + n):
807
+ print (f " Task { n} running. " )
808
+ await asyncio.sleep(1 + n/ 10 )
809
+ print (f " Task { n} done " )
810
+
811
+ async def main ():
812
+ async with asyncio.TaskGroup() as tg: # Context manager pauses until members terminate
813
+ for n in range (4 ):
814
+ tg.create_task(foo(n)) # tg.create_task() creates a member task
815
+ print (" TaskGroup done" ) # All tasks have terminated
816
+
817
+ asyncio.run(main())
818
+ ```
819
+ This more complete example illustrates an exception which is not trapped by the
820
+ member task. Cleanup code on all members runs when the exception occurs,
821
+ followed by exception handling code in ` main() ` .
822
+ ``` python
823
+ import uasyncio as asyncio
824
+ fail = True # Set False to demo normal completion
825
+ async def foo (n ):
826
+ print (f " Task { n} running... " )
827
+ try :
828
+ for x in range (10 + n):
829
+ await asyncio.sleep(1 + n/ 10 )
830
+ if n== 0 and x== 5 and fail:
831
+ raise OSError (" Uncaught exception in task." )
832
+ print (f " Task { n} done " )
833
+ finally :
834
+ print (f " Task { n} cleanup " )
835
+
836
+ async def main ():
837
+ try :
838
+ async with asyncio.TaskGroup() as tg:
839
+ for n in range (4 ):
840
+ tg.create_task(foo(n))
841
+ print (" TaskGroup done" ) # Does not get here if a task throws exception
842
+ except Exception as e:
843
+ print (f ' TaskGroup caught exception: " { e} " ' )
844
+ finally :
845
+ print (" TaskGroup finally" )
846
+
770
847
asyncio.run(main())
771
848
```
772
849
0 commit comments