kopia lustrzana https://github.com/peterhinch/micropython-samples
uasyncio: implement Primitive class.
rodzic
7b021ad645
commit
6853171823
|
@ -1,13 +1,15 @@
|
||||||
# Changes to usayncio
|
# Changes to usayncio
|
||||||
|
|
||||||
This archive contains suggestions for changes to new `uasyncio`:
|
This archive contains suggestions for changes to new `uasyncio`. Item 3 below
|
||||||
|
added 2 Dec.
|
||||||
|
|
||||||
1. Implement as a Python package.
|
1. Implement as a Python package.
|
||||||
2. Implement synchronisation primitives as package modules to conserve RAM.
|
2. Implement synchronisation primitives as package modules to conserve RAM.
|
||||||
3. Add `.priority` method to `Stream` class. Enables I/O to be handled at high
|
3. `Primitive` class has methods common to most synchronisation primitives.
|
||||||
|
4. Add `.priority` method to `Stream` class. Enables I/O to be handled at high
|
||||||
priority on a per-device basis.
|
priority on a per-device basis.
|
||||||
4. Rename task queue class `TQueue` to avoid name clash with Queue primitive.
|
5. Rename task queue class `TQueue` to avoid name clash with Queue primitive.
|
||||||
5. Rename task queue instance to `tqueue` as it is used by primitives.
|
6. Rename task queue instance to `tqueue` as it might be used by primitives.
|
||||||
|
|
||||||
## Minor changes
|
## Minor changes
|
||||||
|
|
||||||
|
|
|
@ -8,6 +8,26 @@ import sys, select
|
||||||
|
|
||||||
type_genf = type((lambda: (yield))) # Type of a generator function upy iss #3241
|
type_genf = type((lambda: (yield))) # Type of a generator function upy iss #3241
|
||||||
|
|
||||||
|
################################################################################
|
||||||
|
# Primitive class embodies methods common to most synchronisation primitives
|
||||||
|
|
||||||
|
class Primitive:
|
||||||
|
def __init__(self):
|
||||||
|
self.waiting = TQueue() # Linked list of Tasks waiting on completion
|
||||||
|
def run_next(self):
|
||||||
|
awt = self.waiting.next
|
||||||
|
if awt: # Schedule next task waiting on primitive
|
||||||
|
tqueue.push_head(self.waiting.pop_head())
|
||||||
|
return awt
|
||||||
|
def run_all(self):
|
||||||
|
while self.waiting.next: # Schedule all tasks waiting on primitive
|
||||||
|
tqueue.push_head(self.waiting.pop_head())
|
||||||
|
def save_current(self): # Postpone currently running task
|
||||||
|
self.waiting.push_head(cur_task)
|
||||||
|
# Set calling task's data to this event that it waits on, to double-link it
|
||||||
|
cur_task.data = self
|
||||||
|
|
||||||
|
|
||||||
################################################################################
|
################################################################################
|
||||||
# Task Queue class renamed to avoid conflict with Queue class
|
# Task Queue class renamed to avoid conflict with Queue class
|
||||||
|
|
||||||
|
@ -462,4 +482,4 @@ class Loop:
|
||||||
def get_event_loop(runq_len=0, waitq_len=0):
|
def get_event_loop(runq_len=0, waitq_len=0):
|
||||||
return Loop()
|
return Loop()
|
||||||
|
|
||||||
version = (3, 0, 0)
|
version = (3, 0, 1)
|
||||||
|
|
|
@ -25,12 +25,12 @@ def launch(func, tup_args):
|
||||||
uasyncio.create_task(res)
|
uasyncio.create_task(res)
|
||||||
|
|
||||||
|
|
||||||
class Barrier():
|
class Barrier(uasyncio.Primitive):
|
||||||
def __init__(self, participants, func=None, args=()):
|
def __init__(self, participants, func=None, args=()):
|
||||||
|
super().__init__()
|
||||||
self._participants = participants
|
self._participants = participants
|
||||||
self._func = func
|
self._func = func
|
||||||
self._args = args
|
self._args = args
|
||||||
self.waiting = uasyncio.TQueue() # Linked list of Tasks waiting on completion of barrier
|
|
||||||
self._reset(True)
|
self._reset(True)
|
||||||
|
|
||||||
def trigger(self):
|
def trigger(self):
|
||||||
|
@ -39,8 +39,7 @@ class Barrier():
|
||||||
if self._func is not None:
|
if self._func is not None:
|
||||||
launch(self._func, self._args)
|
launch(self._func, self._args)
|
||||||
self._reset(not self._down) # Toggle direction and release others
|
self._reset(not self._down) # Toggle direction and release others
|
||||||
while self.waiting.next:
|
self.run_all()
|
||||||
uasyncio.tqueue.push_head(self.waiting.pop_head())
|
|
||||||
|
|
||||||
def __iter__(self): # MicroPython
|
def __iter__(self): # MicroPython
|
||||||
self._update()
|
self._update()
|
||||||
|
@ -48,14 +47,11 @@ class Barrier():
|
||||||
if self._func is not None:
|
if self._func is not None:
|
||||||
launch(self._func, self._args)
|
launch(self._func, self._args)
|
||||||
self._reset(not self._down) # Toggle direction and release others
|
self._reset(not self._down) # Toggle direction and release others
|
||||||
while self.waiting.next:
|
self.run_all()
|
||||||
uasyncio.tqueue.push_head(self.waiting.pop_head())
|
|
||||||
return
|
return
|
||||||
direction = self._down
|
direction = self._down
|
||||||
# Other tasks have not reached barrier, put the calling task on the barrier's waiting queue
|
# Other tasks have not reached barrier, put the calling task on the barrier's waiting queue
|
||||||
self.waiting.push_head(uasyncio.cur_task)
|
self.save_current()
|
||||||
# Set calling task's data to this barrier that it waits on, to double-link it
|
|
||||||
uasyncio.cur_task.data = self
|
|
||||||
yield
|
yield
|
||||||
|
|
||||||
def _reset(self, down):
|
def _reset(self, down):
|
||||||
|
|
|
@ -2,7 +2,7 @@ import uasyncio
|
||||||
import uasyncio.lock
|
import uasyncio.lock
|
||||||
import uasyncio.event
|
import uasyncio.event
|
||||||
|
|
||||||
class Condition():
|
class Condition:
|
||||||
def __init__(self, lock=None):
|
def __init__(self, lock=None):
|
||||||
self.lock = uasyncio.Lock() if lock is None else lock
|
self.lock = uasyncio.Lock() if lock is None else lock
|
||||||
self.events = []
|
self.events = []
|
||||||
|
|
|
@ -1,14 +1,12 @@
|
||||||
import uasyncio
|
import uasyncio
|
||||||
|
|
||||||
# Event class for primitive events that can be waited on, set, and cleared
|
# Event class for primitive events that can be waited on, set, and cleared
|
||||||
class Event:
|
class Event(uasyncio.Primitive):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
|
super().__init__()
|
||||||
self.state = 0 # 0=unset; 1=set
|
self.state = 0 # 0=unset; 1=set
|
||||||
self.waiting = uasyncio.TQueue() # Queue of Tasks waiting on completion of this event
|
def set(self): # Event becomes set, schedule any tasks waiting on it
|
||||||
def set(self):
|
self.run_all()
|
||||||
# Event becomes set, schedule any tasks waiting on it
|
|
||||||
while self.waiting.next:
|
|
||||||
uasyncio.tqueue.push_head(self.waiting.pop_head())
|
|
||||||
self.state = 1
|
self.state = 1
|
||||||
def clear(self):
|
def clear(self):
|
||||||
self.state = 0
|
self.state = 0
|
||||||
|
@ -17,9 +15,7 @@ class Event:
|
||||||
async def wait(self):
|
async def wait(self):
|
||||||
if self.state == 0:
|
if self.state == 0:
|
||||||
# Event not set, put the calling task on the event's waiting queue
|
# Event not set, put the calling task on the event's waiting queue
|
||||||
self.waiting.push_head(uasyncio.cur_task)
|
self.save_current()
|
||||||
# Set calling task's data to this event that it waits on, to double-link it
|
|
||||||
uasyncio.cur_task.data = self
|
|
||||||
yield
|
yield
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
|
@ -7,10 +7,10 @@ import uasyncio
|
||||||
import uasyncio
|
import uasyncio
|
||||||
|
|
||||||
|
|
||||||
class Lock:
|
class Lock(uasyncio.Primitive):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
|
super().__init__()
|
||||||
self._locked = False
|
self._locked = False
|
||||||
self.waiting = uasyncio.TQueue() # Linked list of Tasks waiting on completion of this event
|
|
||||||
self._awt = None # task that is going to acquire the lock. Needed to prevent race
|
self._awt = None # task that is going to acquire the lock. Needed to prevent race
|
||||||
# condition between pushing the next waiting task and the task actually acquiring
|
# condition between pushing the next waiting task and the task actually acquiring
|
||||||
# the lock because during that time another newly started task could acquire the
|
# the lock because during that time another newly started task could acquire the
|
||||||
|
@ -21,8 +21,7 @@ class Lock:
|
||||||
if self._locked or self._awt:
|
if self._locked or self._awt:
|
||||||
# Lock set or just released but has tasks waiting on it,
|
# Lock set or just released but has tasks waiting on it,
|
||||||
# put the calling task on the Lock's waiting queue and yield
|
# put the calling task on the Lock's waiting queue and yield
|
||||||
self.waiting.push_head(uasyncio.cur_task)
|
self.save_current()
|
||||||
uasyncio.cur_task.data = self
|
|
||||||
try:
|
try:
|
||||||
yield
|
yield
|
||||||
except uasyncio.CancelledError:
|
except uasyncio.CancelledError:
|
||||||
|
@ -46,10 +45,8 @@ class Lock:
|
||||||
if not self._locked:
|
if not self._locked:
|
||||||
raise RuntimeError("Lock is not acquired.")
|
raise RuntimeError("Lock is not acquired.")
|
||||||
self._locked = False
|
self._locked = False
|
||||||
self._awt = self.waiting.next # Task which will get lock
|
# Lock becomes available. If task(s) are waiting on it save task which will
|
||||||
if self.waiting.next:
|
self.awt = self.run_next() # get lock and schedule that task
|
||||||
# Lock becomes available, schedule next task waiting on it
|
|
||||||
uasyncio.tqueue.push_head(self.waiting.pop_head())
|
|
||||||
|
|
||||||
async def __aexit__(self, *args):
|
async def __aexit__(self, *args):
|
||||||
return self.release()
|
return self.release()
|
||||||
|
|
|
@ -23,12 +23,12 @@ class QueueFull(Exception):
|
||||||
# interrupted between calling qsize() and doing an operation on the Queue.
|
# interrupted between calling qsize() and doing an operation on the Queue.
|
||||||
|
|
||||||
|
|
||||||
class Queue:
|
class Queue(uasyncio.Primitive):
|
||||||
|
|
||||||
def __init__(self, maxsize=0):
|
def __init__(self, maxsize=0):
|
||||||
|
super().__init__()
|
||||||
self.maxsize = maxsize
|
self.maxsize = maxsize
|
||||||
self._queue = deque((), maxsize)
|
self._queue = deque((), maxsize)
|
||||||
self.waiting = uasyncio.TQueue() # Linked list of Tasks waiting on queue
|
|
||||||
|
|
||||||
def _get(self):
|
def _get(self):
|
||||||
return self._queue.popleft()
|
return self._queue.popleft()
|
||||||
|
@ -36,14 +36,9 @@ class Queue:
|
||||||
async def get(self): # Usage: item = await queue.get()
|
async def get(self): # Usage: item = await queue.get()
|
||||||
if not self._queue:
|
if not self._queue:
|
||||||
# Queue is empty, put the calling Task on the waiting queue
|
# Queue is empty, put the calling Task on the waiting queue
|
||||||
task = uasyncio.cur_task
|
self.save_current()
|
||||||
self.waiting.push_head(task)
|
|
||||||
# Set calling task's data to double-link it
|
|
||||||
task.data = self
|
|
||||||
yield
|
yield
|
||||||
if self.waiting.next:
|
self.run_next() # Task(s) waiting to put on queue, schedule first Task
|
||||||
# Task(s) waiting to put on queue, schedule first Task
|
|
||||||
uasyncio.tqueue.push_head(self.waiting.pop_head())
|
|
||||||
return self._get()
|
return self._get()
|
||||||
|
|
||||||
def get_nowait(self): # Remove and return an item from the queue.
|
def get_nowait(self): # Remove and return an item from the queue.
|
||||||
|
@ -58,14 +53,9 @@ class Queue:
|
||||||
async def put(self, val): # Usage: await queue.put(item)
|
async def put(self, val): # Usage: await queue.put(item)
|
||||||
if self.qsize() >= self.maxsize and self.maxsize:
|
if self.qsize() >= self.maxsize and self.maxsize:
|
||||||
# Queue full, put the calling Task on the waiting queue
|
# Queue full, put the calling Task on the waiting queue
|
||||||
task = uasyncio.cur_task
|
self.save_current()
|
||||||
self.waiting.push_head(task)
|
|
||||||
# Set calling task's data to double-link it
|
|
||||||
uasyncio.cur_task.data = self
|
|
||||||
yield
|
yield
|
||||||
if self.waiting.next:
|
self.run_next() # Task(s) waiting to get from queue, schedule first Task
|
||||||
# Task(s) waiting to get from queue, schedule first Task
|
|
||||||
uasyncio.tqueue.push_head(self.waiting.pop_head())
|
|
||||||
self._put(val)
|
self._put(val)
|
||||||
|
|
||||||
def put_nowait(self, val): # Put an item into the queue without blocking.
|
def put_nowait(self, val): # Put an item into the queue without blocking.
|
||||||
|
|
|
@ -3,10 +3,10 @@
|
||||||
import uasyncio
|
import uasyncio
|
||||||
|
|
||||||
|
|
||||||
class Semaphore():
|
class Semaphore((uasyncio.Primitive)):
|
||||||
def __init__(self, value=1):
|
def __init__(self, value=1):
|
||||||
|
super().__init__()
|
||||||
self._count = value
|
self._count = value
|
||||||
self.waiting = uasyncio.TQueue() # Linked list of Tasks waiting on completion of this event
|
|
||||||
|
|
||||||
async def __aenter__(self):
|
async def __aenter__(self):
|
||||||
await self.acquire()
|
await self.acquire()
|
||||||
|
@ -18,17 +18,13 @@ class Semaphore():
|
||||||
async def acquire(self):
|
async def acquire(self):
|
||||||
if self._count == 0:
|
if self._count == 0:
|
||||||
# Semaphore unavailable, put the calling Task on the waiting queue
|
# Semaphore unavailable, put the calling Task on the waiting queue
|
||||||
self.waiting.push_head(uasyncio.cur_task)
|
self.save_current()
|
||||||
# Set calling task's data to double-link it
|
|
||||||
uasyncio.cur_task.data = self
|
|
||||||
yield
|
yield
|
||||||
self._count -= 1
|
self._count -= 1
|
||||||
|
|
||||||
def release(self):
|
def release(self):
|
||||||
self._count += 1
|
self._count += 1
|
||||||
if self.waiting.next:
|
self.run_next() # Task(s) waiting on semaphore, schedule first Task
|
||||||
# Task(s) waiting on semaphore, schedule first Task
|
|
||||||
uasyncio.tqueue.push_head(self.waiting.pop_head())
|
|
||||||
|
|
||||||
class BoundedSemaphore(Semaphore):
|
class BoundedSemaphore(Semaphore):
|
||||||
def __init__(self, value=1):
|
def __init__(self, value=1):
|
||||||
|
|
Ładowanie…
Reference in New Issue