← index #3241Issue #131
Off-topic · high · value 2.365
QUERY · ISSUE

uasyncio emits no error message if a coro is called rather than awaited.

openby peterhinchopened 2017-07-25updated 2026-03-26
extmod

The following

import uasyncio as asyncio

async def bar():
    await asyncio.sleep(1)
    print('In bar')

async def foo():
    print('Launching bar')
    await bar()
    print('Getting it wrong')
    bar()  # bad

loop = asyncio.get_event_loop()
loop.run_until_complete(foo())

produces this output

>>> import rats37
Launching bar
In bar
Getting it wrong
>>> 

CPython 3.5.2 issues the following error message:

>>> import rats37
Launching bar
In bar
Getting it wrong
/home/adminpete/rats37.py:11: RuntimeWarning: coroutine 'bar' was never awaited
  bar()
>>> 

It would be useful if a message were emitted in response to this error.

CANDIDATE · ISSUE

uasyncio problem with call_soon and args

closedby peterhinchopened 2016-12-09updated 2017-06-14

I'm probably missing something but this worked until I introduced an arg:

import uasyncio as asyncio
async def foo(n):
    arg = await asyncio.sleep(1)
    print("foo {} run {}".format(n, arg))
loop = asyncio.get_event_loop()
arg = 42
loop.call_soon(foo(1), arg)
loop.run_forever()

Outcome (Pyboard and Unix)

Traceback (most recent call last):
  File "<stdin>", line 10, in <module>
  File "/sd/lib/uasyncio/core.py", line 115, in run_forever
  File "/sd/lib/uasyncio/core.py", line 79, in run_forever
TypeError: can't send non-None value to a just-started generator
>>> 
8 comments
pfalcon · 2016-12-09

call_soon() accepts a callback, not a coroutine: https://docs.python.org/3/library/asyncio-eventloop.html#asyncio.AbstractEventLoop.call_soon

peterhinch · 2016-12-10

I'm aware that this is the case in CPython. Things in the code led me to believe that MicroPython deviated from this rule. Firstly a comment in core.py says that ensure_future and Task are deprecated. Further, the implementation these execute call_soon; in these cases call_soon will be fed a coro.

Is EventLoop.create_task the one correct way to launch a coro?

Another question. What is the approved way for a coro to yield to the scheduler efficiently in the case where no specific delay is required (round-robin scheduling)? The MicroPython implementation allows yield which works, but would throw a syntax error in CPython.

A point arose when I ported a demo program from my scheduler. It flashes the Pyboard LED's asynchronously. It is intended to run for a period then stop, which it does via run_until_complete. However if I run it again, the LED's don't flash (unless I soft reset and re-import). To get round this I needed to add an interlock whereby the timing coro flags each flashing coro to terminate. Each reports completion back to the timing routine, allowing it to complete causing the event loop to shut down. This works but is a bit involved for a trivial demo :)

Is there a clean way to shut down an asyncio program so it can be run again at the REPL?

Sorry if I'm asking dumb questions. I'm trying to understand the MicroPython way of using asyncio for hardware interfacing with a view to rendering my scheduler redundant. My plan (once I've got to grips with it) is to write a tutorial which might pre-empt dumb questions from others.

dpgeorge · 2016-12-14

Is EventLoop.create_task the one correct way to launch a coro?

Yes. The fact that create_task calls call_soon (actually call_at) behind the scenes and passes a coro to it is just an implementation detail. You shouldn't rely on this fact; call_soon should only be fed callbacks by the end-user.

peterhinch · 2016-12-14

OK, thanks. What about issuing yield in coros? It's faster than await asyncio.sleep(0); yield n is also more efficient than await asyncio.sleep_ms(n). However these constructs are not allowed in CPython. Will MicroPython continue to support yield in coros?

Secondly, where an asyncio program runs to completion I find that a soft reset is required to run it again unless I include code to ensure every coro completes before the program terminates. This isn't a major issue in that most embedded applications run forever but I'd like to understand why this occurs. And whether this is a bug or a feature to be documented.

dpgeorge · 2016-12-15

OK, thanks. What about issuing yield in coros? It's faster than await asyncio.sleep(0)

I would suggest using await asyncio.sleep(0) for the time being, since that's guaranteed to continue working. Anything else will depend on how asyncio looks when it's more mature.

Secondly, where an asyncio program runs to completion I find that a soft reset is required to run it again

It sounds like there is some left-over state in the uasyncio scheduler. Did you create a new event loop for the second run, or reuse the existing one?

peterhinch · 2016-12-15

If you paste the following on a Pyboard at the REPL, then issue test(10) it flashes the LED's for 10 seconds. If you repeat test(10) it runs for 10 seconds but no LED's flash.

import pyb
import uasyncio as asyncio

async def killer(duration):
    await asyncio.sleep(duration)

async def toggle(objLED, time_ms):
    while True:
        await asyncio.sleep_ms(time_ms)
        objLED.toggle()

def test(duration):
    loop = asyncio.get_event_loop()
    duration = int(duration)
    if duration > 0:
        print("Flash LED's for {:3d} seconds".format(duration))
    leds = [pyb.LED(x) for x in range(1,5)]  # Initialise all four on board LED's
    for x, led in enumerate(leds):           # Create a coroutine for each LED
        t = int((0.2 + x/2) * 1000)
        loop.create_task(toggle(leds[x], t))
    loop.run_until_complete(killer(duration))
    loop.close()
hiway · 2016-12-17

Secondly, where an asyncio program runs to completion I find that a soft reset is required to run it again unless I include code to ensure every coro completes before the program terminates. This isn't a major issue in that most embedded applications run forever but I'd like to understand why this occurs. And whether this is a bug or a feature to be documented.

From the code here: https://github.com/micropython/micropython-lib/blob/master/uasyncio.core/uasyncio/core.py#L162

_event_loop = None
_event_loop_class = EventLoop
def get_event_loop():
    global _event_loop
    if _event_loop is None:
        _event_loop = _event_loop_class()
return _event_loop

_event_loop is a global in the module, and get_event_loop() returns it the next time we call it from an already imported module. I am new to asyncio and uasyncio, however from the examples I have worked with so far, I think this is expected behavior for asyncio - get_event_loop() from any module gets the correct event_loop. It is intuitive when the interpreter quits along with the program.

On long-running interpreters, would it make sense to clean up when uasyncio quits? That would give the expected behavior when using REPL, as observed by @peterhinch .

pfalcon · 2017-06-14

The original issue in the title should be clarified by now. If there're additional issues, please submit them in separate ticket(s) with correspondings titles.

Keyboard

j / / n
next pair
k / / p
previous pair
1 / / h
show query pane
2 / / l
show candidate pane
c
copy suggested comment
r
toggle reasoning
g i
go to index
?
show this help
esc
close overlays

press ? or esc to close

copied