uasyncio: implement Primitive class.

pull/12/head
Peter Hinch 2019-12-02 13:53:29 +00:00
rodzic 7b021ad645
commit 6853171823
8 zmienionych plików z 53 dodań i 56 usunięć

Wyświetl plik

@ -1,13 +1,15 @@
# 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.
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.
4. 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.
5. Rename task queue class `TQueue` to avoid name clash with Queue primitive.
6. Rename task queue instance to `tqueue` as it might be used by primitives.
## Minor changes

Wyświetl plik

@ -8,6 +8,26 @@ import sys, select
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
@ -462,4 +482,4 @@ class Loop:
def get_event_loop(runq_len=0, waitq_len=0):
return Loop()
version = (3, 0, 0)
version = (3, 0, 1)

Wyświetl plik

@ -25,12 +25,12 @@ def launch(func, tup_args):
uasyncio.create_task(res)
class Barrier():
class Barrier(uasyncio.Primitive):
def __init__(self, participants, func=None, args=()):
super().__init__()
self._participants = participants
self._func = func
self._args = args
self.waiting = uasyncio.TQueue() # Linked list of Tasks waiting on completion of barrier
self._reset(True)
def trigger(self):
@ -39,8 +39,7 @@ class Barrier():
if self._func is not None:
launch(self._func, self._args)
self._reset(not self._down) # Toggle direction and release others
while self.waiting.next:
uasyncio.tqueue.push_head(self.waiting.pop_head())
self.run_all()
def __iter__(self): # MicroPython
self._update()
@ -48,14 +47,11 @@ class Barrier():
if self._func is not None:
launch(self._func, self._args)
self._reset(not self._down) # Toggle direction and release others
while self.waiting.next:
uasyncio.tqueue.push_head(self.waiting.pop_head())
self.run_all()
return
direction = self._down
# Other tasks have not reached barrier, put the calling task on the barrier's waiting queue
self.waiting.push_head(uasyncio.cur_task)
# Set calling task's data to this barrier that it waits on, to double-link it
uasyncio.cur_task.data = self
self.save_current()
yield
def _reset(self, down):

Wyświetl plik

@ -2,7 +2,7 @@ import uasyncio
import uasyncio.lock
import uasyncio.event
class Condition():
class Condition:
def __init__(self, lock=None):
self.lock = uasyncio.Lock() if lock is None else lock
self.events = []

Wyświetl plik

@ -1,14 +1,12 @@
import uasyncio
# Event class for primitive events that can be waited on, set, and cleared
class Event:
class Event(uasyncio.Primitive):
def __init__(self):
super().__init__()
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
while self.waiting.next:
uasyncio.tqueue.push_head(self.waiting.pop_head())
def set(self): # Event becomes set, schedule any tasks waiting on it
self.run_all()
self.state = 1
def clear(self):
self.state = 0
@ -17,9 +15,7 @@ class Event:
async def wait(self):
if self.state == 0:
# Event not set, put the calling task on the event's waiting queue
self.waiting.push_head(uasyncio.cur_task)
# Set calling task's data to this event that it waits on, to double-link it
uasyncio.cur_task.data = self
self.save_current()
yield
return True

Wyświetl plik

@ -7,10 +7,10 @@ import uasyncio
import uasyncio
class Lock:
class Lock(uasyncio.Primitive):
def __init__(self):
super().__init__()
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
# 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
@ -21,8 +21,7 @@ class Lock:
if self._locked or self._awt:
# Lock set or just released but has tasks waiting on it,
# put the calling task on the Lock's waiting queue and yield
self.waiting.push_head(uasyncio.cur_task)
uasyncio.cur_task.data = self
self.save_current()
try:
yield
except uasyncio.CancelledError:
@ -46,10 +45,8 @@ class Lock:
if not self._locked:
raise RuntimeError("Lock is not acquired.")
self._locked = False
self._awt = self.waiting.next # Task which will get lock
if self.waiting.next:
# Lock becomes available, schedule next task waiting on it
uasyncio.tqueue.push_head(self.waiting.pop_head())
# Lock becomes available. If task(s) are waiting on it save task which will
self.awt = self.run_next() # get lock and schedule that task
async def __aexit__(self, *args):
return self.release()

Wyświetl plik

@ -23,12 +23,12 @@ class QueueFull(Exception):
# interrupted between calling qsize() and doing an operation on the Queue.
class Queue:
class Queue(uasyncio.Primitive):
def __init__(self, maxsize=0):
super().__init__()
self.maxsize = maxsize
self._queue = deque((), maxsize)
self.waiting = uasyncio.TQueue() # Linked list of Tasks waiting on queue
def _get(self):
return self._queue.popleft()
@ -36,14 +36,9 @@ class Queue:
async def get(self): # Usage: item = await queue.get()
if not self._queue:
# Queue is empty, put the calling Task on the waiting queue
task = uasyncio.cur_task
self.waiting.push_head(task)
# Set calling task's data to double-link it
task.data = self
self.save_current()
yield
if self.waiting.next:
# Task(s) waiting to put on queue, schedule first Task
uasyncio.tqueue.push_head(self.waiting.pop_head())
self.run_next() # Task(s) waiting to put on queue, schedule first Task
return self._get()
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)
if self.qsize() >= self.maxsize and self.maxsize:
# Queue full, put the calling Task on the waiting queue
task = uasyncio.cur_task
self.waiting.push_head(task)
# Set calling task's data to double-link it
uasyncio.cur_task.data = self
self.save_current()
yield
if self.waiting.next:
# Task(s) waiting to get from queue, schedule first Task
uasyncio.tqueue.push_head(self.waiting.pop_head())
self.run_next() # Task(s) waiting to get from queue, schedule first Task
self._put(val)
def put_nowait(self, val): # Put an item into the queue without blocking.

Wyświetl plik

@ -3,10 +3,10 @@
import uasyncio
class Semaphore():
class Semaphore((uasyncio.Primitive)):
def __init__(self, value=1):
super().__init__()
self._count = value
self.waiting = uasyncio.TQueue() # Linked list of Tasks waiting on completion of this event
async def __aenter__(self):
await self.acquire()
@ -18,17 +18,13 @@ class Semaphore():
async def acquire(self):
if self._count == 0:
# Semaphore unavailable, put the calling Task on the waiting queue
self.waiting.push_head(uasyncio.cur_task)
# Set calling task's data to double-link it
uasyncio.cur_task.data = self
self.save_current()
yield
self._count -= 1
def release(self):
self._count += 1
if self.waiting.next:
# Task(s) waiting on semaphore, schedule first Task
uasyncio.tqueue.push_head(self.waiting.pop_head())
self.run_next() # Task(s) waiting on semaphore, schedule first Task
class BoundedSemaphore(Semaphore):
def __init__(self, value=1):