← index #15187Issue #16759
Related · high · value 2.496
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

Asyncio task scheduling issue when using run_until_completed

closedby yoctopuceopened 2025-02-15updated 2025-05-07
bug

Port, board and/or hardware

any port with asyncio support

MicroPython version

micropython-1.25.0 running on win32 [MSC v.1916 64 bit]

Reproduction

The issue is linked to the fact that run_until_complete does not complete the processing of the awaited task before returning to hte caller. If other pending tasks are waiting it, they will be lost forever.

The code below will reproduce the issue.

  • When the tasks are await-ed form an async function, everything works as expected (function async_test)
  • When the tasks are started from a sync function using run_until_complete, scheduling is broken (function sync_test)
    This issue is specific to micropython asyncio implementation of run_until_complete and does not occur in CPython.
import sys, asyncio, random

def get_event_loop():
    if sys.implementation.name == "micropython":
        return asyncio.get_event_loop()
    else:
        try:
            eventloop = asyncio.get_running_loop()
        except RuntimeError:
            eventloop = asyncio.new_event_loop()
        return eventloop

class Worker:
    def __init__(self):
        self._eventLoop = None
        self._tasks = []

    def launchTask(self, asyncJob):
        if self._eventLoop is None:
            self._eventLoop = get_event_loop()
        return self._eventLoop.create_task(asyncJob)

    async def job(self, prerequisite, taskName):
        if prerequisite:
            await prerequisite
        await asyncio.sleep(2)
        print(taskName, "work completed")

    def planTasks(self):
        self._tasks.append(self.launchTask(self.job(None, 'task0')))
        self._tasks.append(self.launchTask(self.job(self._tasks[0], 'task1')))
        self._tasks.append(self.launchTask(self.job(self._tasks[1], 'task2')))

    async def waitForTask(self, taskIdx):
        return await self._tasks[taskIdx]

    def syncWaitForTask(self, taskIdx):
        return self._eventLoop.run_until_complete(self._tasks[taskIdx])

async def async_test():
    print("--- Running the async test")
    worker = Worker()
    worker.planTasks()
    await worker.waitForTask(0)
    print("-> task0 done")
    await worker.waitForTask(2)
    print("-> task2 done")

def sync_test():
    print("--- Running the sync test")
    worker = Worker()
    worker.planTasks()
    worker.syncWaitForTask(0)
    print("-> task0 done")
    worker.syncWaitForTask(2)
    print("-> task2 done")

asyncio.run(async_test())
sync_test()

Expected behaviour

--- Running the async test
task0 work completed
-> task0 done
task1 work completed
task2 work completed
-> task2 done
--- Running the sync test
task0 work completed
-> task0 done
task1 work completed
task2 work completed
-> task2 done

Observed behaviour

--- Running the async test
task0 work completed
-> task0 done
task1 work completed
task2 work completed
-> task2 done
--- Running the sync test
task0 work completed
-> task0 done
-> task2 done

Additional Information

I will submit a pull request with a suggested fix

Code of Conduct

Yes, I agree

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