← index #15187Issue #5881
Related · high · value 1.850
QUERY · ISSUE

asyncio: Calling .run() within a task does not behave as per CPython

openby peterhinchopened 2024-06-02updated 2026-03-25
bugextmod

Port, board and/or hardware

Unix build

MicroPython version

MicroPython v1.23.0-preview.72.g4a2e510a8.dirty on 2024-02-04; linux [GCC 11.4.0] version

Reproduction

import asyncio

async def foo(s):
    while True:
        print(f"Task {s}")
        await asyncio.sleep(1)

async def main():
    asyncio.create_task(foo(1))
    await asyncio.sleep(2)
    asyncio.run(foo(2))  # CPython throws a RuntimeError
    await asyncio.sleep(2)
    asyncio.create_task(foo(1))
    await asyncio.sleep(5)
    print("Done")  # never happens in MP

asyncio.run(main())

Expected behaviour

On CPython issuing run() in a running task throws a RuntimeError:

Python 3.10.12 (main, Nov 20 2023, 15:14:05) [GCC 11.4.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import rats17
Task 1
Task 1
Task 1
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/home/adminpete/rats17.py", line 17, in <module>
    asyncio.run(main())
  File "/usr/lib/python3.10/asyncio/runners.py", line 44, in run
    return loop.run_until_complete(main)
  File "/usr/lib/python3.10/asyncio/base_events.py", line 649, in run_until_complete
    return future.result()
  File "/home/adminpete/rats17.py", line 11, in main
    asyncio.run(foo(2))  # CPython throws a RuntimeError
  File "/usr/lib/python3.10/asyncio/runners.py", line 33, in run
    raise RuntimeError(
RuntimeError: asyncio.run() cannot be called from a running event loop
>>> 

Observed behaviour

The run() command appears to work, starting the second foo instance. However main never terminates.

MicroPython v1.23.0-preview.72.g4a2e510a8.dirty on 2024-02-04; linux [GCC 11.4.0] version
Use Ctrl-D to exit, Ctrl-E for paste mode
>>> import rats17
Task 1
Task 1
Task 1
Task 2
Task 1
Task 2
Task 1
Task 2
Task 1
Task 2
... continues forever

Additional Information

This arose from this issue.

A possible fix to asyncio.core.py is

def run(coro):
    if cur_task is None:
        return run_until_complete(create_task(coro))
    else:
        raise RuntimeError("asyncio.run() cannot be called from a running event loop")

Code of Conduct

Yes, I agree

CANDIDATE · ISSUE

extmod/uasyncio Discrepancy between behaviour and docs

closedby peterhinchopened 2020-04-08updated 2020-04-08

The doc for the class Task states:

Task.cancel()

Cancel the task by injecting a CancelledError into it. The task may or may not ignore this exception.

The following script produces identical results in CPython 3.8 and MicroPython, with no exception occurring in bar(). The script runs correctly apart from this.

Is there in fact a way to detect cancellation within a task, or are the docs wrong?

try:
    import uasyncio as asyncio
except ImportError:
    import asyncio

async def bar():
    try:
        while True:
            print('Bar running')
            await asyncio.sleep(1)
    except Exception as e:
        print('bar stopped.', e)  # Never happens (CPython or mp)

async def main():
    t = asyncio.create_task(bar())
    await asyncio.sleep(2)
    t.cancel()
    await asyncio.sleep(1)

asyncio.run(main())

Detecting cancellation or timeouts within a task is actually very useful e.g. for cleanup, if there is a way to achieve it.

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