asyncio await a task

General discussions and questions abound development of code with MicroPython that is not hardware specific.
Target audience: MicroPython Users.
Post Reply
GordanTrevis
Posts: 6
Joined: Sat Aug 24, 2019 8:59 am

asyncio await a task

Post by GordanTrevis » Sat Aug 24, 2019 9:16 am

I'm new to asyncio and want to try it for Micropython on an ESP8266.
I'm trying to get 2 LEDs flashing at the same time.
The Problem: I can't figure out how to await multiple task bevor continuing the code.
I don't understand the example from Peterhinch well described GitHub documentation "4.1 Awaitable classes".


Code: Select all

##### ASYINC ####

from machine import Pin
import time
import utime 
import uasyncio as asyncio

redLed = Pin(5, Pin.OUT)
greenLed = Pin(4, Pin.OUT)

async def blinkRed():
    '''
     Blink Red LED 10 Times
    '''
    count = 0
    print("Start: Blink Red Led 10 Times")
    
    while count < 10:
        redLed.on()
        time.sleep(1)
        redLed.off()
        time.sleep(1)
        count += 1

    return print("Done: blinking Red LED {} Times".format(count))    

async def blinkGreen():
    '''
     Blink Green LED 15 Times
    '''
    count = 0
    print("Start: Blink Green Led 15 Times")

    while count < 15:
        greenLed.on()
        time.sleep(1)
        greenLed.off()
        time.sleep(1)
        count += 1

    return print("Done: blinking Green LED {} Times".format(count))   

        

async def main():
    global loob
    start = utime.ticks_ms()

    task1 = loop.create_task(blinkGreen())
    task2 = loop.create_task(blinkRed())

    # await asyncio.wait([task1, task2]) // Method from standart asyinc lib
    #
    # await task1  
    # 
    # await yield from task1
    
    end = utime.ticks_ms()
    div = (utime.ticks_diff(end,start))/1000
    return print("Finished main in {} Seconds".format(div))


# Start

loop = asyncio.get_event_loop()
loop.run_until_complete(main())
loop.close()
How can I await task1 and task2 as I would do in the standard library?

Online
User avatar
jimmo
Posts: 553
Joined: Tue Aug 08, 2017 1:57 am
Location: Sydney, Australia

Re: asyncio await a task

Post by jimmo » Sat Aug 24, 2019 10:11 am

You need to use asyncio.sleep rather than time.sleep.

time.sleep will sleep the microcontroller, whereas asyncio.sleep will yield the coroutine and schedule it to be resumed in the future.

GordanTrevis
Posts: 6
Joined: Sat Aug 24, 2019 8:59 am

Re: asyncio await a task

Post by GordanTrevis » Sat Aug 24, 2019 6:55 pm

Shure, That's True!

But the problem is how I correctly substitute the "await asyncio.wait([task1, task2])" line in Micropython.

Right now the script would finish in 2ms since it does not wait for the tasks to be finished.

Online
User avatar
jimmo
Posts: 553
Joined: Tue Aug 08, 2017 1:57 am
Location: Sydney, Australia

Re: asyncio await a task

Post by jimmo » Sun Aug 25, 2019 4:26 am

What you need is CPython's `asyncio.gather` but unfortunately that's not available (yet) in uasyncio. (asyncio.wait is a more low-level thing -- gather does exactly what you want -- turn multiple awaitable things into a single awaitable thing)

In the meantime though (until this is added to uasyncio), another excellent resource from Peter's asyncio resources & tutorial is `asyn.py` which provides a bunch of useful helpers for coroutine synchronisation, including an implementation of gather-like functionality. The usage is slightly different to CPython's asyncio.gather but works really well!

Code: Select all

import asyn

async def main():
  ...
  # Note blinkGreen not blinkGreen() as it would be with CPython asyncio.gather
  await asyn.Gather([asyn.Gatherable(blinkGreen), asyn.Gatherable(blinkRed)])
(and the change I suggested earlier to use `await asyncio.sleep(1)`, should be all you need)

GordanTrevis
Posts: 6
Joined: Sat Aug 24, 2019 8:59 am

Re: asyncio await a task

Post by GordanTrevis » Sun Aug 25, 2019 8:38 am

Thanks! This method works almost fine!
(I did not know that asyn was a extra script which has to be loaded)

If I "import asyn" my ESP8266(D1mini) throughs a MemoryError (with no additional information).
If I try multiple times, it eventually imports an empty asyn.
To clarify I created an asyn.py file including the corresponding code in the root directory next to main.py

Code: Select all

>>> dir(asyn)
['__class__', '__name__']
I tried the Gather method by copying the needed Classes into the main file. Which then works fine.
What is causing the Error? Im sure it is on my site.

( FYI: I also ran the script with `await asyncio.sleep(1)` only, and it finished in 1ms ;) - But still, Thank you!)

Online
User avatar
jimmo
Posts: 553
Joined: Tue Aug 08, 2017 1:57 am
Location: Sydney, Australia

Re: asyncio await a task

Post by jimmo » Sun Aug 25, 2019 9:08 am

When MicroPython loads code from the filesystem, it takes temporary RAM to load the contents of the file, then permanently uses RAM to store the compiled bytecode (which is what it subsequently executes). So by reducing the amount of code you reduce the likely hood of running out of memory.

So just grabbing the Gather bits out of asyn.py is a good idea. Also, having many shorter files helps because only one file has to be in memory at once (and the bytecode is typically more compact).

The other option is to "freeze" the libraries into your firmware (so this way the bytecode executes from flash instead of RAM) but this requires building your own firmware image. (It's a very handy option to have though)

Can you post your exact code. I tested this myself and it worked fine -- "Finished main in 30.0 seconds"

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

Re: asyncio await a task

Post by pythoncoder » Sun Aug 25, 2019 11:05 am

Freezing is best but another option is to cross compile the module and copy the .mpy file to the target (deleting the target's .py file).

When a .py file is imported it is compiled to bytecode: this uses RAM and is the usual cause of memory errors on import. Loading a .mpy file removes the need for this step.

See https://github.com/micropython/micropyt ... /README.md.
Peter Hinch

GordanTrevis
Posts: 6
Joined: Sat Aug 24, 2019 8:59 am

Re: asyncio await a task

Post by GordanTrevis » Mon Aug 26, 2019 8:24 am

gotcha! I did not know you can bring the Bords to its limits this easily - but makes absolute sense.

Freezing sounds incredibly cool and useful! Thank you for that!

I somehow figured out how to create a .mpy file, and as expected it works and imports like a charm. Thank you for that info too!
( For future readers with a similar problem: I followed this Medium Article, and the GitHub instruction)

Thank you, Guys!

and Finally here is the exact prior code:
>> Finished main in 0.001 Seconds
(always grateful for input on my coding "skills")

Code: Select all

import time
import utime
import uasyncio as asyncio
from machine import Pin

redLed = Pin(5, Pin.OUT)
greenLed = Pin(4, Pin.OUT)

async def blinkRed():
    '''
     Blink Red LED 10 Times
    '''
    count = 0
    print("Start: Blink Red Led 10 Times")

    while count < 10:
        redLed.on()
        #time.sleep(1)
        await asyncio.sleep(1)
        redLed.off()
        #time.sleep(1)
        await asyncio.sleep(1)
        count += 1
    print("Done: blinking Red LED {} Times".format(count))
    #return "Red"

async def blinkGreen():
    '''
     Blink Green LED 15 Times
    '''
    count = 0
    print("Start: Blink Green Led 15 Times")

    while count < 15:
        greenLed.on()
        #time.sleep(1)
        await asyncio.sleep(1)
        greenLed.off()
        #time.sleep(1)
        await asyncio.sleep(1)
        count += 1

    print("Done: blinking Green LED {} Times".format(count))
    #return 15



async def main():
    global loob
    start = utime.ticks_ms()

    #task1 = loop.create_task(blinkGreen())
    #task2 = loop.create_task(blinkRed())

    # await asyncio.wait([task1, task2]) // Method from standart asyinc lib
    #
    # await task1
    #
    # await yield from task1
    # res = await Gather([Gatherable(blinkGreen), Gatherable(blinkRed)])

    end = utime.ticks_ms()
    div = (utime.ticks_diff(end,start))/1000
    # print(res)
    return print("Finished main in {} Seconds".format(div))


# Start

loop = asyncio.get_event_loop()
loop.run_until_complete(main())
loop.close()

Online
User avatar
jimmo
Posts: 553
Joined: Tue Aug 08, 2017 1:57 am
Location: Sydney, Australia

Re: asyncio await a task

Post by jimmo » Mon Aug 26, 2019 9:48 am

GordanTrevis wrote:
Mon Aug 26, 2019 8:24 am
# res = await Gather([Gatherable(blinkGreen), Gatherable(blinkRed)])
This line shouldn't be commented out?

GordanTrevis
Posts: 6
Joined: Sat Aug 24, 2019 8:59 am

Re: asyncio await a task

Post by GordanTrevis » Mon Aug 26, 2019 9:57 am

The Gather Class is from the asyn libary which eventualy fixed the problem.
But the library was not implemented in the initial code of the problem (first post).

Post Reply