← index #9737Issue #16759
Off-topic · high · value 1.989
QUERY · ISSUE

asyncio implement cpython all_tasks

openby ThinkTransitopened 2022-10-25updated 2022-10-25
enhancement

It would be very useful to have the ability to get a list of all tasks similar to the cpython implementation.

all_tasks

Currently to track all running tasks, tasks must be stored in a global list variable which is not ideal and error prone.

  • Would you be willing to sponsor this work? Yes
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