Reputation: 321
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')
Upvotes: 6
Views: 24015
Reputation: 3867
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)
Upvotes: 0
Reputation: 111
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 !!")
Upvotes: 4
Reputation: 27684
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())
Upvotes: 5
Reputation: 522587
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.
Upvotes: 2