“Fire and forget” python async/await

后端 未结 4 1697
遥遥无期
遥遥无期 2020-11-22 14:19

Sometimes there is some non-critical asynchronous operation that needs to happen but I don\'t want to wait for it to complete. In Tornado\'s coroutine implementation you ca

相关标签:
4条回答
  • 2020-11-22 14:25

    Thank you Sergey for the succint answer. Here is the decorated version of the same.

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

    Produces

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

    Note: Check my other answer which does the same using plain threads.

    0 讨论(0)
  • 2020-11-22 14:28

    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 here
    
    
    fire_and_forget(foo)
    
    0 讨论(0)
  • 2020-11-22 14:39

    For some reason if you are unable to use asyncio then here is the implementation using plain threads. Check my other answers and Sergey's answer too.

    import threading
    
    def fire_and_forget(f):
        def wrapped():
            threading.Thread(target=f).start()
    
        return wrapped
    
    @fire_and_forget
    def foo():
        time.sleep(1)
        print("foo() completed")
    
    print("Hello")
    foo()
    print("I didn't wait for foo()")
    
    0 讨论(0)
  • 2020-11-22 14:46

    Upd:

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


    asyncio.Task to “fire and forget”

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

    import asyncio
    
    
    async 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 1
    async_foo started
    Do some actions 2
    async_foo done
    Do some actions 3
    

    What if tasks are executing after event loop complete?

    Note that asyncio expects task would be completed at the moment event loop completed. 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 event loop 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 asyncio
    from contextlib import suppress
    
    
    async 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 1
    echo
    Do some actions 2
    echo
    Do some actions 3
    echo
    
    0 讨论(0)
提交回复
热议问题