Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I schedule awaitables for sequential execution without awaiting?

Suppose, I have a few async functions, f1, f2 and f3. I want to execute these functions in a sequential order. The easiest way to do this would be to await on them:

async def foo():
    await f1()
    # Do something else
    await f2()
    # Do something else
    await f3()
    # Do something else

However, I don't care about the results of these async functions, and I would like to continue with the execution of the rest of the function after scheduling the async functions.

From the asyncio tasks documentation it seems that asyncio.ensure_future() can help me with this. I used the following code to test this out, and the synchronous parts of foo() as per my expectations. However, bar() never executes past asyncio.sleep()

import asyncio

async def bar(name):
    print(f'Sleep {name}')
    await asyncio.sleep(3)
    print(f'Wakeup {name}')

async def foo():
    print('Enter foo')

    for i in [1, 2, 3]:
        asyncio.ensure_future(bar(i))
        print(f'Scheduled bar({i}) for execution')

    print('Exit foo')

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

The output for the above code:

Enter foo
Scheduled bar(1) for execution
Scheduled bar(2) for execution
Scheduled bar(3) for execution
Exit foo
Sleep 1
Sleep 2
Sleep 3

So, what is the proper method to do what I'm looking for?


1 Answers

I have a few async functions, f1, f2 and f3. I want to execute these functions in a sequential order. [...] I would like to continue with the execution of the rest of the function after scheduling the async functions.

The straightforward way to do this is by using a helper function and letting it run it in the background:

async def foo():
    async def run_fs():
        await f1()
        await f2()
        await f3()
    loop = asyncio.get_event_loop()
    loop.create_task(run_fs())
    # proceed with stuff that foo needs to do
    ...

create_task submits a coroutine to the event loop. You can also use ensure_future for that, but create_task is preferred when spawning a coroutine.

The code in the question has two issues: first, the functions are not run sequentially, but in parallel. This is fixed as shown above, by running a single async function in the background that awaits the three in order. The second problem is that in asyncio run_until_complete(foo()) only waits for foo() to finish, not also for the tasks spawned by foo (though there are asyncio alternatives that address this). If you want run_until_complete(foo()) to wait for run_fs to finish, foo has to await it itself.

Fortunately, that is trivial to implement - just add another await at the end of foo(), awaiting the task created for run_fs earlier. If the task is already done by that point, the await will exit immediately, otherwise it will wait.

async def foo():
    async def run_fs():
        await f1()
        await f2()
        await f3()
    loop = asyncio.get_event_loop()
    f_task = loop.create_task(run_fs())
    # proceed with stuff that foo needs to do
    ...
    # finally, ensure that the fs have finished by the time we return
    await f_task
like image 88
user4815162342 Avatar answered Oct 22 '25 03:10

user4815162342



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!