@@ -99,10 +99,14 @@ $ python3 -m micropip.py install -p ~/syn micropython-uasyncio
99
99
100
100
4.1 [ Awaitable classes] ( ./TUTORIAL.md#41-awaitable-classes )
101
101
102
+ 4.1.1 [ Use in context managers] ( ./TUTORIAL.md#411-use-in-context-managers )
103
+
104
+ 4.1.2 [ Awaiting a coro] ( ./TUTORIAL.md#412-awaiting-a-coro )
105
+
102
106
4.2 [ Asynchronous iterators] ( ./TUTORIAL.md#42-asynchronous-iterators )
103
107
104
108
4.3 [ Asynchronous context managers] ( ./TUTORIAL.md#43-asynchronous-context-managers )
105
-
109
+
106
110
4.4 [ Coroutines with timeouts] ( ./TUTORIAL.md#44-coroutines-with-timeouts )
107
111
108
112
4.5 [ Exceptions] ( ./TUTORIAL.md#45-exceptions )
@@ -430,7 +434,8 @@ ineffective. It will not receive the `TimeoutError` until it has acquired the
430
434
lock. The same observation applies to task cancellation.
431
435
432
436
The module ` asyn.py ` offers a ` Lock ` class which works in these situations. It
433
- is significantly less efficient than the official class.
437
+ is significantly less efficient than the official class but supports additional
438
+ interfaces as per the CPython version including context manager usage.
434
439
435
440
###### [ Contents] ( ./TUTORIAL.md#contents )
436
441
@@ -495,11 +500,14 @@ compensation for this.
495
500
496
501
## 3.3 Barrier
497
502
498
- This enables multiple coros to rendezvous at a particular point. For example
499
- producer and consumer coros can synchronise at a point where the producer has
500
- data available and the consumer is ready to use it. At that point in time the
501
- ` Barrier ` can optionally run a callback before releasing the barrier and
502
- allowing all waiting coros to continue. [ Full details.] ( ./PRIMITIVES.md#34-class-barrier )
503
+ This has two uses. Firstly it can cause a coro to pause until one or more other
504
+ coros have terminated.
505
+
506
+ Secondly it enables multiple coros to rendezvous at a particular point. For
507
+ example producer and consumer coros can synchronise at a point where the
508
+ producer has data available and the consumer is ready to use it. At that point
509
+ in time the ` Barrier ` can run an optional callback before the barrier is
510
+ released and all waiting coros can continue. [ Full details.] ( ./PRIMITIVES.md#34-class-barrier )
503
511
504
512
The callback can be a function or a coro. In most applications a function is
505
513
likely to be used: this can be guaranteed to run to completion before the
@@ -674,24 +682,91 @@ class Foo():
674
682
for n in range (5 ):
675
683
print (' __await__ called' )
676
684
yield from asyncio.sleep(1 ) # Other coros get scheduled here
685
+ return 42
677
686
678
687
__iter__ = __await__ # See note below
679
688
680
689
async def bar ():
681
690
foo = Foo() # Foo is an awaitable class
682
691
print (' waiting for foo' )
683
- await foo
684
- print (' done' )
692
+ res = await foo # Retrieve result
693
+ print (' done' , res )
685
694
686
695
loop = asyncio.get_event_loop()
687
696
loop.run_until_complete(bar())
688
697
```
689
698
690
699
Currently MicroPython doesn't support ` __await__ ` (issue #2678 ) and
691
700
` __iter__ ` must be used. The line ` __iter__ = __await__ ` enables portability
692
- between CPython and MicroPython.
701
+ between CPython and MicroPython. Example code may be found in the ` Event ` ,
702
+ ` Barrier ` , ` Cancellable ` and ` Condition ` classes in asyn.py.
703
+
704
+ ### 4.1.1 Use in context managers
705
+
706
+ Awaitable objects can be used in synchronous or asynchronous CM's by providing
707
+ the necessary special methods. The syntax is:
708
+
709
+ ``` python
710
+ with await awaitable as a: # The 'as' clause is optional
711
+ # code omitted
712
+ async with awaitable as a: # Asynchronous CM (see below)
713
+ # do something
714
+ ```
715
+
716
+ To achieve this the ` __await__ ` generator should return ` self ` . This is passed
717
+ to any variable in an ` as ` clause and also enables the special methods to work.
718
+ See ` asyn.Condition ` and ` asyntest.condition_test ` , where the ` Condition ` class
719
+ is awaitable and may be used in a synchronous CM.
720
+
721
+ ###### [ Contents] ( ./TUTORIAL.md#contents )
722
+
723
+ ### 4.1.2 Awaiting a coro
724
+
725
+ The Python language requires that ` __await__ ` is a generator function. In
726
+ MicroPython generators and coroutines are identical, so the solution is to use
727
+ ` yield from coro(args) ` .
728
+
729
+ This tutorial aims to offer code portable to CPython 3.5 or above. In CPython
730
+ coroutines and generators are distinct. CPython coros have an ` __await__ `
731
+ special method which retrieves a generator. This is portable:
732
+
733
+ ``` python
734
+ up = False # Running under MicroPython?
735
+ try :
736
+ import uasyncio as asyncio
737
+ up = True # Or can use sys.implementation.name
738
+ except ImportError :
739
+ import asyncio
693
740
694
- Example code may be found in the ` Event ` and ` Barrier ` classes in asyn.py.
741
+ async def times_two (n ): # Coro to await
742
+ await asyncio.sleep(1 )
743
+ return 2 * n
744
+
745
+ class Foo ():
746
+ def __await__ (self ):
747
+ res = 1
748
+ for n in range (5 ):
749
+ print (' __await__ called' )
750
+ if up: # MicroPython
751
+ res = yield from times_two(res)
752
+ else : # CPython
753
+ res = yield from times_two(res).__await__ ()
754
+ return res
755
+
756
+ __iter__ = __await__
757
+
758
+ async def bar ():
759
+ foo = Foo() # foo is awaitable
760
+ print (' waiting for foo' )
761
+ res = await foo # Retrieve value
762
+ print (' done' , res)
763
+
764
+ loop = asyncio.get_event_loop()
765
+ loop.run_until_complete(bar())
766
+ ```
767
+
768
+ Note that, in ` __await__ ` , ` yield from asyncio.sleep(1) ` is allowed by CPython.
769
+ I haven't yet established how this is achieved.
695
770
696
771
###### [ Contents] ( ./TUTORIAL.md#contents )
697
772
@@ -761,18 +836,22 @@ async def bar(lock):
761
836
As with normal context managers an exit method is guaranteed to be called when
762
837
the context manager terminates, whether normally or via an exception. To
763
838
achieve this the special methods ` __aenter__ ` and ` __aexit__ ` must be
764
- defined, both being coros waiting on an ` awaitable ` object. This example comes
765
- from the ` Lock ` class:
839
+ defined, both being coros waiting on a coro or ` awaitable ` object. This example
840
+ comes from the ` Lock ` class:
766
841
767
842
``` python
768
843
async def __aenter__ (self ):
769
844
await self .acquire() # a coro defined with async def
845
+ return self
770
846
771
847
async def __aexit__ (self , * args ):
772
848
self .release() # A conventional method
773
849
await asyncio.sleep_ms(0 )
774
850
```
775
851
852
+ If the ` async with ` has an ` as variable ` clause the variable receives the
853
+ value returned by ` __aenter__ ` .
854
+
776
855
Note there is currently a bug in the implementation whereby if an explicit
777
856
` return ` is issued within an ` async with ` block, the ` __aexit__ ` method
778
857
is not called. The solution is to design the code so that in all cases it runs
0 commit comments