Skip to content

Commit f7c44fa

Browse files
committed
Move Message class and docs to threadsafe.
1 parent 4dfecdd commit f7c44fa

File tree

6 files changed

+178
-98
lines changed

6 files changed

+178
-98
lines changed

v3/docs/THREADING.md

Lines changed: 169 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,11 @@ It is not an introduction into ISR coding. For this see
1111
and [this doc](https://github.com/peterhinch/micropython-async/blob/master/v3/docs/INTERRUPTS.md)
1212
which provides specific guidance on interfacing `uasyncio` with ISR's.
1313

14+
Because of [this issue](https://github.com/micropython/micropython/issues/7965)
15+
the `ThreadSafeFlag` class does not work under the Unix build. The classes
16+
presented here depend on this: none can be expected to work on Unix until this
17+
is fixed.
18+
1419
# Contents
1520

1621
1. [Introduction](./THREADING.md#1-introduction) The various types of pre-emptive code.
@@ -25,6 +30,8 @@ which provides specific guidance on interfacing `uasyncio` with ISR's.
2530
     2.2.3 [Object ownership](./THREADING.md#223-object-ownership)
2631
3. [Synchronisation](./THREADING.md#3-synchronisation)
2732
3.1 [Threadsafe Event](./THREADING.md#31-threadsafe-event)
33+
3.2 [Message](./THREADING.md#32-message) A threadsafe event with data payload.
34+
4. [Taming blocking functions](./THREADING.md#4-taming-blocking-functions)
2835

2936
# 1. Introduction
3037

@@ -376,3 +383,165 @@ async def main():
376383

377384
asyncio.run(main())
378385
```
386+
## 3.2 Message
387+
388+
The `Message` class uses [ThreadSafeFlag](./TUTORIAL.md#36-threadsafeflag) to
389+
provide an object similar to `Event` with the following differences:
390+
391+
* `.set()` has an optional data payload.
392+
* `.set()` can be called from another thread, another core, or from an ISR.
393+
* It is an awaitable class.
394+
* Payloads may be retrieved in an asynchronous iterator.
395+
* Multiple tasks can wait on a single `Message` instance.
396+
397+
Constructor:
398+
* No args.
399+
400+
Synchronous methods:
401+
* `set(data=None)` Trigger the `Message` with optional payload (may be any
402+
Python object).
403+
* `is_set()` Returns `True` if the `Message` is set, `False` if `.clear()` has
404+
been issued.
405+
* `clear()` Clears the triggered status. At least one task waiting on the
406+
message should issue `clear()`.
407+
* `value()` Return the payload.
408+
409+
Asynchronous Method:
410+
* `wait()` Pause until message is triggered. You can also `await` the message
411+
as per the examples.
412+
413+
The `.set()` method can accept an optional data value of any type. The task
414+
waiting on the `Message` can retrieve it by means of `.value()` or by awaiting
415+
the `Message` as below. A `Message` can provide a means of communication from
416+
an interrupt handler and a task. The handler services the hardware and issues
417+
`.set()` which causes the waiting task to resume (in relatively slow time).
418+
419+
This illustrates basic usage:
420+
```python
421+
import uasyncio as asyncio
422+
from threadsafe import Message
423+
424+
async def waiter(msg):
425+
print('Waiting for message')
426+
res = await msg
427+
print('waiter got', res)
428+
msg.clear()
429+
430+
async def main():
431+
msg = Message()
432+
asyncio.create_task(waiter(msg))
433+
await asyncio.sleep(1)
434+
msg.set('Hello') # Optional arg
435+
await asyncio.sleep(1)
436+
437+
asyncio.run(main())
438+
```
439+
The following example shows multiple tasks awaiting a `Message`.
440+
```python
441+
from threadsafe import Message
442+
import uasyncio as asyncio
443+
444+
async def bar(msg, n):
445+
while True:
446+
res = await msg
447+
msg.clear()
448+
print(n, res)
449+
# Pause until other coros waiting on msg have run and before again
450+
# awaiting a message.
451+
await asyncio.sleep_ms(0)
452+
453+
async def main():
454+
msg = Message()
455+
for n in range(5):
456+
asyncio.create_task(bar(msg, n))
457+
k = 0
458+
while True:
459+
k += 1
460+
await asyncio.sleep_ms(1000)
461+
msg.set('Hello {}'.format(k))
462+
463+
asyncio.run(main())
464+
```
465+
Receiving messages in an asynchronous iterator:
466+
```python
467+
import uasyncio as asyncio
468+
from threadsafe import Message
469+
470+
async def waiter(msg):
471+
async for text in msg:
472+
print(f"Waiter got {text}")
473+
msg.clear()
474+
475+
async def main():
476+
msg = Message()
477+
task = asyncio.create_task(waiter(msg))
478+
for text in ("Hello", "This is a", "message", "goodbye"):
479+
msg.set(text)
480+
await asyncio.sleep(1)
481+
task.cancel()
482+
await asyncio.sleep(1)
483+
print("Done")
484+
485+
asyncio.run(main())
486+
```
487+
The `Message` class does not have a queue: if the instance is set, then set
488+
again before it is accessed, the first data item will be lost.
489+
490+
# 4. Taming blocking functions
491+
492+
Blocking functions or methods have the potential of stalling the `uasyncio`
493+
scheduler. Short of rewriting them to work properly the only way to tame them
494+
is to run them in another thread. The following is a way to achieve this.
495+
```python
496+
async def unblock(func, *args, **kwargs):
497+
def wrap(func, message, args, kwargs):
498+
message.set(func(*args, **kwargs)) # Run the blocking function.
499+
msg = Message()
500+
_thread.start_new_thread(wrap, (func, msg, args, kwargs))
501+
return await msg
502+
```
503+
Given a blocking function `blocking` taking two positional and two keyword args
504+
it may be awaited in a `uasyncio` task with
505+
```python
506+
res = await unblock(blocking, 1, 2, c = 3, d = 4)
507+
```
508+
The function runs "in the background" with other tasks running; only the
509+
calling task is paused. Note how the args are passed. There is a "gotcha" which
510+
is cancellation. It is not valid to cancel the `unblock` task because the
511+
underlying thread will still be running. There is no general solution to this.
512+
If the specific blocking function has a means of interrupting it or of forcing
513+
a timeout then it may be possible to code a solution.
514+
515+
The following is a complete example where blocking is demonstrated with
516+
`time.sleep`.
517+
```python
518+
import uasyncio as asyncio
519+
from threadsafe import Message
520+
import _thread
521+
from time import sleep
522+
523+
def slow_add(a, b, *, c, d): # Blocking function.
524+
sleep(5)
525+
return a + b + c + d
526+
527+
# Convert a blocking function to a nonblocking one using threading.
528+
async def unblock(func, *args, **kwargs):
529+
def wrap(func, message, args, kwargs):
530+
message.set(func(*args, **kwargs)) # Run the blocking function.
531+
msg = Message()
532+
_thread.start_new_thread(wrap, (func, msg, args, kwargs))
533+
return await msg
534+
535+
async def busywork(): # Prove uasyncio is running.
536+
while True:
537+
print("#", end="")
538+
await asyncio.sleep_ms(200)
539+
540+
async def main():
541+
bw = asyncio.create_task(busywork())
542+
res = await unblock(slow_add, 1, 2, c = 3, d = 4)
543+
bw.cancel()
544+
print(f"\nDone. Result = {res}")
545+
546+
asyncio.run(main())
547+
```

v3/docs/TUTORIAL.md

Lines changed: 7 additions & 96 deletions
Original file line numberDiff line numberDiff line change
@@ -1343,106 +1343,17 @@ finally:
13431343

13441344
## 3.9 Message
13451345

1346-
Because of [this issue](https://github.com/micropython/micropython/issues/7965)
1347-
the `Message` class does not work under the Unix build.
1346+
The `Message` class uses [ThreadSafeFlag](./TUTORIAL.md#36-threadsafeflag) to
1347+
provide an object similar to `Event` with the following differences:
13481348

1349-
This is an unofficial primitive with no counterpart in CPython asyncio. It uses
1350-
[ThreadSafeFlag](./TUTORIAL.md#36-threadsafeflag) to provide an object similar
1351-
to `Event` but capable of being set in a hard ISR context. It extends
1352-
`ThreadSafeFlag` so that multiple tasks can wait on an ISR.
1353-
1354-
It is similar to the `Event` class. It differs in that:
13551349
* `.set()` has an optional data payload.
1356-
* `.set()` is capable of being called from a hard or soft interrupt service
1357-
routine.
1350+
* `.set()` can be called from another thread, another core, or from an ISR.
13581351
* It is an awaitable class.
1359-
* It can be used in an asynchronous iterator.
1360-
* The logic of `.clear` differs: it must be called by at least one task which
1361-
waits on the `Message`.
1362-
1363-
The `.set()` method can accept an optional data value of any type. The task
1364-
waiting on the `Message` can retrieve it by means of `.value()` or by awaiting
1365-
the `Message` as below.
1366-
1367-
Like `Event`, `Message` provides a way for a task to pause until another flags it
1368-
to continue. A `Message` object is instantiated and made accessible to the task
1369-
using it:
1370-
1371-
```python
1372-
import uasyncio as asyncio
1373-
from primitives import Message
1374-
1375-
async def waiter(msg):
1376-
print('Waiting for message')
1377-
res = await msg
1378-
print('waiter got', res)
1379-
msg.clear()
1380-
1381-
async def main():
1382-
msg = Message()
1383-
asyncio.create_task(waiter(msg))
1384-
await asyncio.sleep(1)
1385-
msg.set('Hello') # Optional arg
1386-
await asyncio.sleep(1)
1387-
1388-
asyncio.run(main())
1389-
```
1390-
A `Message` can provide a means of communication between an interrupt handler
1391-
and a task. The handler services the hardware and issues `.set()` which causes
1392-
the waiting task to resume (in relatively slow time).
1393-
1394-
Constructor:
1395-
* No args.
1352+
* Payloads may be retrieved in an asynchronous iterator.
1353+
* Multiple tasks can wait on a single `Message` instance.
13961354

1397-
Synchronous methods:
1398-
* `set(data=None)` Trigger the `Message` with optional payload (may be any
1399-
Python object).
1400-
* `is_set()` Returns `True` if the `Message` is set, `False` if `.clear()` has
1401-
been issued.
1402-
* `clear()` Clears the triggered status. At least one task waiting on the
1403-
message should issue `clear()`.
1404-
* `value()` Return the payload.
1405-
1406-
Asynchronous Method:
1407-
* `wait()` Pause until message is triggered. You can also `await` the message
1408-
as per the examples.
1409-
1410-
The following example shows multiple tasks awaiting a `Message`.
1411-
```python
1412-
from primitives import Message
1413-
import uasyncio as asyncio
1414-
1415-
async def bar(msg, n):
1416-
while True:
1417-
res = await msg
1418-
msg.clear()
1419-
print(n, res)
1420-
# Pause until other coros waiting on msg have run and before again
1421-
# awaiting a message.
1422-
await asyncio.sleep_ms(0)
1423-
1424-
async def main():
1425-
msg = Message()
1426-
for n in range(5):
1427-
asyncio.create_task(bar(msg, n))
1428-
k = 0
1429-
while True:
1430-
k += 1
1431-
await asyncio.sleep_ms(1000)
1432-
msg.set('Hello {}'.format(k))
1433-
1434-
asyncio.run(main())
1435-
```
1436-
Receiving messages in an asynchronous iterator:
1437-
```python
1438-
msg = Message()
1439-
asyncio.create_task(send_data(msg))
1440-
async for data in msg:
1441-
# process data
1442-
msg.clear()
1443-
```
1444-
The `Message` class does not have a queue: if the instance is set, then set
1445-
again before it is accessed, the first data item will be lost.
1355+
It may be found in the `threadsafe` directory and is documented
1356+
[here](./THREADING.md#32-message).
14461357

14471358
## 3.10 Synchronising to hardware
14481359

v3/primitives/__init__.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,6 @@ def _handle_exception(loop, context):
3636
"Condition": "condition",
3737
"Delay_ms": "delay_ms",
3838
"Encode": "encoder_async",
39-
"Message": "message",
4039
"Pushbutton": "pushbutton",
4140
"ESP32Touch": "pushbutton",
4241
"Queue": "queue",

v3/primitives/tests/asyntest.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818

1919
from primitives import Barrier, Semaphore, BoundedSemaphore, Condition, Queue, RingbufQueue
2020
try:
21-
from primitives import Message
21+
from threadsafe import Message
2222
except:
2323
pass
2424

v3/threadsafe/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
_attrs = {
1212
"ThreadSafeEvent": "threadsafe_event",
1313
"ThreadSafeQueue": "threadsafe_queue",
14+
"Message": "message",
1415
}
1516

1617
# Copied from uasyncio.__init__.py
File renamed without changes.

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy