Some problems with asyn library

Discussion about programs, libraries and tools that work with MicroPython. Mostly these are provided by a third party.
Target audience: All users and developers of MicroPython.
User avatar
on4aa
Posts: 70
Joined: Sat Nov 11, 2017 8:41 pm
Location: Europe
Contact:

Some problems with asyn library

Post by on4aa » Fri Jan 26, 2018 8:42 pm

I am experiencing trouble with the following basic uasyncio event loop.
It runs, taking a sample every 3 seconds, but update_lcd() runs only once instead of once after every sample.
What am I doing wrong?

Code: Select all

import uasyncio as asyncio
import asyn    # https://github.com/peterhinch/micropython-async/blob/master/asyn.py
import sys

async def sample(data_ready):
    while True:
        print('Sample taken')
        data_ready.set()
        await asyncio.sleep(3)    # in s

async def update_lcd(data_ready):
    await data_ready
    print('LCD updated')
    data_ready.clear()

loop = asyncio.get_event_loop()
data_ready = asyn.Event()
loop.create_task(sample(data_ready))
loop.create_task(update_lcd(data_ready))

try:
    loop.run_forever()
except KeyboardInterrupt or SystemExit:
    sys.exit()
The output of this is:
Sample taken
LCD updated
Sample taken
Sample taken
Sample taken
Serge

cefn
Posts: 230
Joined: Tue Aug 09, 2016 10:58 am

Re: uasyncio — basic event loop

Post by cefn » Fri Jan 26, 2018 9:08 pm

I can't assess the eventing logic, not really being very familiar with using asyn, but surely both function definitions need to have a while loop in them. Without the while loop, you are specifically asking for the steps in the update_lcd() coroutine function to be run only once.

User avatar
on4aa
Posts: 70
Joined: Sat Nov 11, 2017 8:41 pm
Location: Europe
Contact:

Re: uasyncio — basic event loop

Post by on4aa » Fri Jan 26, 2018 10:13 pm

@cefn
Thanks, that did it! :D
This one of my first experiments with uasyncio and asyn.py, so your help is greatly appreciated.

I now changed update_lcd() to:

Code: Select all

async def update_lcd(data_ready):
    while True:
        await data_ready
        print('LCD updated')
        data_ready.clear()
By the way, asyn.py is available at:
https://github.com/peterhinch/micropyth ... er/asyn.py
Serge

User avatar
on4aa
Posts: 70
Joined: Sat Nov 11, 2017 8:41 pm
Location: Europe
Contact:

Re: uasyncio — basic event loop

Post by on4aa » Fri Jan 26, 2018 10:23 pm

I am testing this on MicroPython for Unix.
With the code now running, I am noticing that one of my CPU cores enters into 100% usage.
Honestly, I was not expecting this from uasyncio with all these await statements present.

This problem most probably can be solved with placing one or more utime.sleep(0.050) statements.
The question is where?
Serge

User avatar
on4aa
Posts: 70
Joined: Sat Nov 11, 2017 8:41 pm
Location: Europe
Contact:

Re: uasyncio — basic event loop

Post by on4aa » Fri Jan 26, 2018 10:31 pm

The culprit for the CPU usage appears to be update_lcd().

EDIT: I tried several things to reduce the CPU usage, but still no luck.
This is currently what I have:

Code: Select all

import uasyncio as asyncio
import asyn    # https://github.com/peterhinch/micropython-async/blob/master/asyn.py
import sys

async def sample(data_ready):
    while True:
        print('Sample taken')
        data_ready.set()
        await asyncio.sleep(3)    # in s

async def update_lcd(data_ready):
    while True:
        await data_ready
        print('LCD updated')
        data_ready.clear()

loop = asyncio.get_event_loop()
data_ready = asyn.Event()
loop.create_task(sample(data_ready))
loop.create_task(update_lcd(data_ready))

try:
    loop.run_forever()
except KeyboardInterrupt or SystemExit:
    sys.exit()
Serge

User avatar
pythoncoder
Posts: 5956
Joined: Fri Jul 18, 2014 8:01 am
Location: UK
Contact:

Re: uasyncio — basic event loop

Post by pythoncoder » Sat Jan 27, 2018 7:24 am

Awaiting an Event causes a loop to run which affectively calls await uasyncio.sleep(0). This is to achieve the fastest response possible (given the inherent constraints of cooperative scheduling).

I'm not sure why you're concerned about CPU usage. If there are other coroutines running a loop with await uasyncio.sleep(0) they will get scheduled in round-robin fashion along with the Event task. Running a zero delay is an entirely legitimate way to use uasyncio and is the way to achieve round-robin scheduling. It will monopolise the CPU but it is doing useful work running the tasks.

Putting utime.sleep(0.050) in is a bad idea because it will slow everything down and waste CPU cycles.

Perhaps you could explain what you're trying to achieve regarding CPU usage.
Peter Hinch
Index to my micropython libraries.

User avatar
on4aa
Posts: 70
Joined: Sat Nov 11, 2017 8:41 pm
Location: Europe
Contact:

Re: uasyncio — basic event loop

Post by on4aa » Sat Jan 27, 2018 9:30 am

Dear Peter, I beg to disagree on the zero sleep value in Event.
The code should run equally well on a CPU-based system with MicroPython Unix as on a MCU-based board.
Wasting less electricity and a laptop cooling fan that does not spin up to full speed are two good enough reasons to improve on this.

The following CPython StackExchange answer clearly demonstrates how introducing tiny sleep amounts dramatically reduces the CPU usage without significantly perturbing ordinary program execution.

So yes, I would be a proponent of changing that line in Event to await uasyncio.sleep_ms(50).
Serge

User avatar
pythoncoder
Posts: 5956
Joined: Fri Jul 18, 2014 8:01 am
Location: UK
Contact:

Re: uasyncio — basic event loop

Post by pythoncoder » Sat Jan 27, 2018 11:31 am

The focus of my efforts on uasyncio is firmware. Firstly because many (most?) nontrivial firmware applications use cooperative scheduling. And secondly because MicroPython is mainly targeted at bare metal platforms. In my view the main purpose of the Unix port is for development of code which will eventually run on bare metal.

Most firmware applications prioritise performance over CPU utilisation. Further issuing sleep(0) is the way to set up round-robin scheduling. So doing this is normal practice.

An option might be to have an Event class variable which determines the delay period.

Alas I'm unconvinced that a nonzero delay will fix the problem. The scheduler in uasyncio doesn't invoke any power saving modes. It runs continuously calling wait() which calls poller.ipoll(). To analyse this properly would involve studying the source for poller.ipoll(). I'd need some persuasion that there was a real need for this.
Peter Hinch
Index to my micropython libraries.

User avatar
on4aa
Posts: 70
Joined: Sat Nov 11, 2017 8:41 pm
Location: Europe
Contact:

Re: uasyncio — basic event loop

Post by on4aa » Sat Jan 27, 2018 1:24 pm

Peter, I completely agree with setting MCUs as the main target.
However, it might well be that we are currently unaware of some problems simply because MCUs are typically less dramatic than CPUs (no excessive heating, no fans), or because CPU tools like htop are —as far as I know— unavailable.

Since many MCU projects are battery operated, these software design questions are certainly very relevant.

I ordered a Sjaming JT-7 USB tester to measure the power consumption of my contraptions.
However, it will still take a while for it to arrive.
Serge

cefn
Posts: 230
Joined: Tue Aug 09, 2016 10:58 am

Re: uasyncio — basic event loop

Post by cefn » Sat Jan 27, 2018 3:47 pm

I have to say I agree with Peter, given that...

a) the issue you raise arises for the desktop ports of Micropython, where sleep actually translates into a low-power mode, which is not the development target for Micropython.
b) utime.sleep() can be invoked within a coroutine as a trivial workaround for any case where you care about achieving CPU idleness, e.g. triggering low-power mode on a desktop

Simply create an 'uncooperative' coroutine function which is scheduled in the round-robin and will block the thread with a utime.sleep() call. Then you have your behaviour, but without lodging it deep in a library where it isn't appropriate. Additionally, you can see explicitly how much idle delay you are choosing to introduce to all the waiting asyncio calls which share a round robin.

Incidentally, in Micropython ports having pre-emptive threading support, wouldn't asyncio's default behaviour be wasteful in the sense of reducing responsiveness of other threads with its busy-wait? Given the triviality of a workaround I don't think this is an issue, but just putting it out there.

Post Reply