"Fire and forget" python async/await "Fire and forget" python async/await python python

"Fire and forget" python async/await


Upd:

Replace asyncio.ensure_future with asyncio.create_task everywhere if you're using Python >= 3.7 It's a newer, nicer way to spawn tasks.


asyncio.Task to "fire and forget"

According to python docs for asyncio.Task it is possible to start some coroutine to execute "in the background". The task created by asyncio.ensure_future won't block the execution (therefore the function will return immediately!). This looks like a way to "fire and forget" as you requested.

import asyncioasync def async_foo():    print("async_foo started")    await asyncio.sleep(1)    print("async_foo done")async def main():    asyncio.ensure_future(async_foo())  # fire and forget async_foo()    # btw, you can also create tasks inside non-async funcs    print('Do some actions 1')    await asyncio.sleep(1)    print('Do some actions 2')    await asyncio.sleep(1)    print('Do some actions 3')if __name__ == '__main__':    loop = asyncio.get_event_loop()    loop.run_until_complete(main())

Output:

Do some actions 1async_foo startedDo some actions 2async_foo doneDo some actions 3

What if tasks are executing after the event loop has completed?

Note that asyncio expects tasks to be completed at the moment the event loop completes. So if you'll change main() to:

async def main():    asyncio.ensure_future(async_foo())  # fire and forget    print('Do some actions 1')    await asyncio.sleep(0.1)    print('Do some actions 2')

You'll get this warning after the program finished:

Task was destroyed but it is pending!task: <Task pending coro=<async_foo() running at [...]

To prevent that you can just await all pending tasks after the event loop has completed:

async def main():    asyncio.ensure_future(async_foo())  # fire and forget    print('Do some actions 1')    await asyncio.sleep(0.1)    print('Do some actions 2')if __name__ == '__main__':    loop = asyncio.get_event_loop()    loop.run_until_complete(main())        # Let's also finish all running tasks:    pending = asyncio.Task.all_tasks()    loop.run_until_complete(asyncio.gather(*pending))

Kill tasks instead of awaiting them

Sometimes you don't want to await tasks to be done (for example, some tasks may be created to run forever). In that case, you can just cancel() them instead of awaiting them:

import asynciofrom contextlib import suppressasync def echo_forever():    while True:        print("echo")        await asyncio.sleep(1)async def main():    asyncio.ensure_future(echo_forever())  # fire and forget    print('Do some actions 1')    await asyncio.sleep(1)    print('Do some actions 2')    await asyncio.sleep(1)    print('Do some actions 3')if __name__ == '__main__':    loop = asyncio.get_event_loop()    loop.run_until_complete(main())    # Let's also cancel all running tasks:    pending = asyncio.Task.all_tasks()    for task in pending:        task.cancel()        # Now we should await task to execute it's cancellation.        # Cancelled task raises asyncio.CancelledError that we can suppress:        with suppress(asyncio.CancelledError):            loop.run_until_complete(task)

Output:

Do some actions 1echoDo some actions 2echoDo some actions 3echo


Output:

>>> Hello>>> foo() started>>> I didn't wait for foo()>>> foo() completed

Here is the simple decorator function which pushes the execution to background and line of control moves to next line of the code.

The primary advantage is, you don't have to declare the function as await

import asyncioimport timedef fire_and_forget(f):    def wrapped(*args, **kwargs):        return asyncio.get_event_loop().run_in_executor(None, f, *args, *kwargs)    return wrapped@fire_and_forgetdef foo():    print("foo() started")    time.sleep(1)    print("foo() completed")print("Hello")foo()print("I didn't wait for foo()")

Note: Check my other answer which does the same using plain thread without asyncio.


This is not entirely asynchronous execution, but maybe run_in_executor() is suitable for you.

def fire_and_forget(task, *args, **kwargs):    loop = asyncio.get_event_loop()    if callable(task):        return loop.run_in_executor(None, task, *args, **kwargs)    else:            raise TypeError('Task must be a callable')def foo():    #asynchronous stuff herefire_and_forget(foo)