简体   繁体   中英

Python call callback after async function is done

I'm trying to call a callback once a async function is done running

Here's an example of what im trying to do:

import asyncio

async def asyncfunction():
    print('Hello')
    await asyncio.sleep(10)
    print('World')
    return 10

def callback(n):
    print(f'The async function returned: {n}')

loop = asyncio.get_event_loop()

# Will block the print until everything is done
callback(loop.run_until_complete(asyncfunction()))
print('Hey')

Here's what that does:

Hello
World
The async function returned: 10
Hey

And here's what I want it to do
Edit: The position of the 'Hey' doesn't really matter, as long as it doesn't have to wait for the async function to be done

Hello
Hey
World
The async function returned: 10

Edit: after some testing I have found a way that does what I want, although I don't know if its the best way to do it

import asyncio
import threading

async def asyncfunction():
    print('Hello')
    await asyncio.sleep(10)
    print('World')
    return 10

def callback(n):
    print(f'The async function returned: {n}')

def wrapper(loop):
    callback(loop.run_until_complete(asyncfunction()))

loop = asyncio.get_event_loop()
thr = threading.Thread(target=wrapper,args=(loop,))
thr.start()
print('Hey')

Using Threading with asyncio is just confusing and most likely not what you want. run_until_complete is one of the blocking call and should likely be the last statement in an asyncio program.

To add code after calling an async function, just create create a wrapper

async def myfunc():
  n = await asyncfunction()
  callback(n)

loop.run_until_complete(myfunc()) # from python 3.7, asyncio.run(myfunc())

If you just want to schedule some code to run asynchronously and continue with something else, create a task and await at the end

async def a_main():
  task = asyncio.ensure_future(myfunc()) # from python 3.7, asyncio.create_task(...)
  print("Hey")
  # Anything else to run
  await task # wait for the task to complete

loop.run_until_complete(a_main())

DISCLAMER: Following code creates different threads for each function.

This might be useful for some of the cases as it is simpler to use. But know that it is not async but gives illusion of async using multiple threads, even though decorator suggests that.

To make any function non blocking, simply copy the decorator and decorate any function with a callback function as parameter. The callback function will receive the data returned from the function.

import asyncio
import requests


def run_async(callback):
    def inner(func):
        def wrapper(*args, **kwargs):
            def __exec():
                out = func(*args, **kwargs)
                callback(out)
                return out

            return asyncio.get_event_loop().run_in_executor(None, __exec)

        return wrapper

    return inner


def _callback(*args):
    print(args)


# Must provide a callback function, callback func will be executed after the func completes execution !!
@run_async(_callback)
def get(url):
    return requests.get(url)


get("https://google.com")
print("Non blocking code ran !!")

To get that order, you need to continue executing the coroutine after print('Hey') . You also need that 'Hey' printed in the "lull" while asyncfunction is sleeping. That can essentially only be scheduled by the event loop itself; since asyncfunction is a black box for all you know and you don't know what it's waiting for or why or can get control back from it explicitly while it's sleeping.

So, execute both asyncfunction and print('Hey') as asynchronous tasks, and mostly hope that the scheduling works out so that 'Hey' is scheduled to run while asyncfunction is sleeping.

val, *_ = loop.run_until_complete(asyncio.gather(
    asyncfunction(),
    asyncio.coroutine(print)('Hey')
))
callback(val)

asyncio.coroutine(print) turns print into an async function, and gather schedules both on the event loop simultaneously, and it will probably work out that print will be executed while asyncfunction is sleeping.

Here is my solution, along with a tool function named run_with_callback

# problem

async def asyncfunction(n):
    print(f'before sleep in asyncfunction({ n })')
    await asyncio.sleep(1)
    print(f'after sleep in asyncfunction({ n })')
    return f'result of asyncfunction({ n })'

def callback(r):
    print(f'inside callback, got: {r}')


# straightforward solution

import asyncio

async def wrapper(n):
    r = await asyncfunction(n)
    callback(r)

asyncio.get_event_loop().create_task(wrapper(1))
print('sync code following loop.create_task(1)')

asyncio.get_event_loop().create_task(wrapper(2))
print('sync code following loop.create_task(2)')


# general solution with a tool function

def run_with_callback(co):
    def wrapper(callback):
        task = asyncio.get_event_loop().create_task(co)
        task.add_done_callback(lambda t: callback(t.result()))
        return callback
    return wrapper

# use as function

run_with_callback(asyncfunction(3))(callback)
print('sync code following loop.create_task(3)')

# use as decorator

@run_with_callback(asyncfunction(4))
def _tmp_callback_1(r):
    callback(r)
print('sync code following loop.create_task(4)')

# main

_all = asyncio.gather(*asyncio.all_tasks(asyncio.get_event_loop()))
asyncio.get_event_loop().run_until_complete(_all)

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM