Skip to content

Commit bdc5eeb

Browse files
committed
Improvements to delay_ms.py
1 parent 4f2aac0 commit bdc5eeb

File tree

2 files changed

+76
-50
lines changed

2 files changed

+76
-50
lines changed

v3/docs/TUTORIAL.md

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1123,6 +1123,8 @@ Methods:
11231123
5. `rvalue` No argument. If a timeout has occurred and a callback has run,
11241124
returns the return value of the callback. If a coroutine was passed, returns
11251125
the `Task` instance. This allows the `Task` to be cancelled or awaited.
1126+
6. `wait` One or more tasks may wait on a `Delay_ms` instance. Execution will
1127+
proceed when the instance has timed out.
11261128

11271129
In this example a `Delay_ms` instance is created with the default duration of
11281130
1s. It is repeatedly triggered for 5 secs, preventing the callback from
@@ -1150,6 +1152,32 @@ try:
11501152
finally:
11511153
asyncio.new_event_loop() # Clear retained state
11521154
```
1155+
This example illustrates multiple tasks waiting on a `Delay_ms`. No callback is
1156+
used.
1157+
```python
1158+
import uasyncio as asyncio
1159+
from primitives.delay_ms import Delay_ms
1160+
1161+
async def foo(n, d):
1162+
await d.wait()
1163+
print('Done in foo no.', n)
1164+
1165+
async def my_app():
1166+
d = Delay_ms()
1167+
for n in range(4):
1168+
asyncio.create_task(foo(n, d))
1169+
d.trigger(3000)
1170+
print('Waiting on d')
1171+
await d.wait()
1172+
print('Done in my_app.')
1173+
await asyncio.sleep(1)
1174+
print('Test complete.')
1175+
1176+
try:
1177+
asyncio.run(my_app())
1178+
finally:
1179+
_ = asyncio.new_event_loop() # Clear retained state
1180+
```
11531181

11541182
## 3.9 Message
11551183

v3/primitives/delay_ms.py

Lines changed: 48 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -1,69 +1,67 @@
1-
# delay_ms.py
1+
# delay_ms.py Now uses ThreadSafeFlag and has extra .wait() API
2+
# Usage:
3+
# from primitives.delay_ms import Delay_ms
24

3-
# Copyright (c) 2018-2020 Peter Hinch
5+
# Copyright (c) 2018-2021 Peter Hinch
46
# Released under the MIT License (MIT) - see LICENSE file
5-
# Rewritten for uasyncio V3. Allows stop time to be brought forwards.
67

78
import uasyncio as asyncio
89
from utime import ticks_add, ticks_diff, ticks_ms
9-
from micropython import schedule
1010
from . import launch
11-
# Usage:
12-
# from primitives.delay_ms import Delay_ms
1311

1412
class Delay_ms:
15-
verbose = False # verbose and can_alloc retained to avoid breaking code.
16-
def __init__(self, func=None, args=(), can_alloc=True, duration=1000):
13+
14+
class DummyTimer: # Stand-in for the timer class. Can be cancelled.
15+
def cancel(self):
16+
pass
17+
_fake = DummyTimer()
18+
19+
def __init__(self, func=None, args=(), duration=1000):
1720
self._func = func
1821
self._args = args
19-
self._duration = duration # Default duration
20-
self._tstop = None # Stop time (ms). None signifies not running.
21-
self._tsave = None # Temporary storage for stop time
22-
self._ktask = None # timer task
23-
self._retrn = None # Return value of launched callable
24-
self._do_trig = self._trig # Avoid allocation in .trigger
22+
self._durn = duration # Default duration
23+
self._retn = None # Return value of launched callable
24+
self._tend = None # Stop time (absolute ms).
25+
self._busy = False
26+
self._trig = asyncio.ThreadSafeFlag()
27+
self._tout = asyncio.Event() # Timeout event
28+
self.wait = self._tout.wait # Allow: await wait_ms.wait()
29+
self._ttask = self._fake # Timer task
30+
asyncio.create_task(self._run())
2531

26-
def stop(self):
27-
if self._ktask is not None:
28-
self._ktask.cancel()
32+
async def _run(self):
33+
while True:
34+
await self._trig.wait() # Await a trigger
35+
self._ttask.cancel() # Cancel and replace
36+
await asyncio.sleep_ms(0)
37+
dt = max(ticks_diff(self._tend, ticks_ms()), 0) # Beware already elapsed.
38+
self._ttask = asyncio.create_task(self._timer(dt))
2939

30-
def trigger(self, duration=0): # Update end time
31-
now = ticks_ms()
32-
if duration <= 0: # Use default set by constructor
33-
duration = self._duration
34-
self._retrn = None
35-
is_running = self()
36-
tstop = self._tstop # Current stop time
37-
# Retriggering normally just updates ._tstop for ._timer
38-
self._tstop = ticks_add(now, duration)
39-
# Identify special case where we are bringing the end time forward
40-
can = is_running and duration < ticks_diff(tstop, now)
41-
if not is_running or can:
42-
schedule(self._do_trig, can)
40+
async def _timer(self, dt):
41+
await asyncio.sleep_ms(dt)
42+
self._tout.set() # Only gets here if not cancelled.
43+
self._tout.clear()
44+
self._busy = False
45+
if self._func is not None:
46+
self._retn = launch(self._func, self._args)
4347

44-
def _trig(self, can):
45-
if can:
46-
self._ktask.cancel()
47-
self._ktask = asyncio.create_task(self._timer(can))
48+
# API
49+
# trigger may be called from hard ISR.
50+
def trigger(self, duration=0): # Update absolute end time, 0-> ctor default
51+
self._tend = ticks_add(ticks_ms(), duration if duration > 0 else self._durn)
52+
self._retn = None # Default in case cancelled.
53+
self._busy = True
54+
self._trig.set()
55+
56+
def stop(self):
57+
self._ttask.cancel()
58+
self._ttask = self._fake
59+
self._busy = False
4860

4961
def __call__(self): # Current running status
50-
return self._tstop is not None
62+
return self._busy
5163

5264
running = __call__
5365

5466
def rvalue(self):
55-
return self._retrn
56-
57-
async def _timer(self, restart):
58-
if restart: # Restore cached end time
59-
self._tstop = self._tsave
60-
try:
61-
twait = ticks_diff(self._tstop, ticks_ms())
62-
while twait > 0: # Must loop here: might be retriggered
63-
await asyncio.sleep_ms(twait)
64-
twait = ticks_diff(self._tstop, ticks_ms())
65-
if self._func is not None: # Timed out: execute callback
66-
self._retrn = launch(self._func, self._args)
67-
finally:
68-
self._tsave = self._tstop # Save in case we restart.
69-
self._tstop = None # timer is stopped
67+
return self._retn

0 commit comments

Comments
 (0)