How can I call an async function without await?
Asked Answered
P

6

76

I have a controller action in aiohttp application.

async def handler_message(request):

    try:
        content = await request.json()
        perform_message(x,y,z)
    except (RuntimeError):
        print("error in perform fb message")
    finally:
        return web.Response(text="Done")

perform_message is async function. Now, when I call action I want that my action return as soon as possible and perform_message put in event loop.

In this way, perform_message isn't executed

Purveyance answered 19/6, 2017 at 12:32 Comment(1)
In JS, calling async funtion without await ahead is to run it without waiting for the return( in the next JS main loop maybe) and it will continue to execute the following code. That's intuitive and simple. But in python calling async function just returns a coroutine object, we need extra code to make it run.Nephew
C
87

One way would be to use create_task function:

import asyncio

async def handler_message(request):
    ...
    loop = asyncio.get_event_loop()
    loop.create_task(perform_message(x,y,z))
    ...

As per the loop documentation, starting Python 3.10, asyncio.get_event_loop() is deprecated. If you're trying to get a loop instance from a coroutine/callback, you should use asyncio.get_running_loop() instead. This method will not work if called from the main thread, in which case a new loop must be instantiated:

loop = asyncio.new_event_loop()
asyncio.set_event_loop(loop)
loop.create_task(perform_message(x, y, z))
loop.run_forever()

Furthermore, if the call is only made once throughout your program's runtime and no other loop needs to be is instantiated (unlikely), you may use:

asyncio.run(perform_message(x, y, z))

This function creates an event loop and terminates it once the coroutine ends, therefore should only be used in the aforementioned scenario.

Cerate answered 19/6, 2017 at 12:43 Comment(28)
@Jellaba create_task method returns a Task object: task = loop.create_task(...). You can store it somewhere and await later.Cerate
Yes, but it is still not started until awaited, right? What if you want to create it, start it, do something else and then await it (it might possibly be done and if not, it blocks until complete)Jellaba
@Jellaba no, create_task will start it as soon as possible. I encourage you to test it (you don't have to await the result at all).Cerate
@Cerate No create_task doesn't start it as soon as possible you need to await for it in order to be executed.Asaph
@HossamAl-Dokkani Dude, how about you check it before commenting? It does start the task. You don't have to await it. Period.Cerate
@Cerate I did bro, here is the code snippet import aiohttp import asyncio async def main(url): ` async with aiohttp.ClientSession() as session:` ` await session.get(url)` loop = asyncio.get_event_loop() loop.create_task(main('http://localhost:4000/test/'))Asaph
@HossamAl-Dokkani that doesn't even start the loop. You need loop.run_forever() at the endCerate
@Cerate but I don't want to run this forever I need my function to terminate and respond with a HttpResponse and I don't want to await for the result.Asaph
@Cerate Can I do this?Asaph
@HossamAl-Dokkani Listen, there's no way to run an async function on a not-running loop. You have to start the loop. You either do it by loop.run_until_complete(coro) or by loop.run_forever(). See this: docs.python.org/3/library/… In your case it seems you are looking for the first option.Cerate
I donvoted the answer since this code does not run tasks immediately. Even if loop is running. Checked this on python 3.7Rebato
@ЯктенсТид the OP wants to run the task in the event loop, presumably to schedule it for execution, not immediately. We have almost none control over the event loop and when it executes tasks. Also define "immediately" because in a multi-task environment you hardly ever guarantee such thing.Cerate
@Cerate What do you mean by "We have almost none control over the event loop and when it executes tasks."? It is software development. You MUST have control over your code and it's executions. Assuming I want to run certain task immediately (which pulls data by url). What should I do in this case? If we really don't have control over loop then I'll use threads in this case.Rebato
@Cerate by 'immediately' I mean that my function should at least start executing - i.e go to the first non-async line in debugger. Yeah, we can't control await order, but in my case function is not running at allRebato
@ЯктенсТид you have no control over threads as well. It is up to OS to decide when will it run your code. Things get even worse due to GIL. The Python's event loop is single threaded by design. It won't run tasks in parallel. However it is concurrency what we aim for, i.e. waiting until current function exits should be a submillisecond delay, which is typically not an issue at all,. And no, we have no control over it. Just like we have no control over 99% of software we use. If you want full control, you have to reimplement everything by yourself. That would be a nightmare.Cerate
@ЯктенсТид if your code doesn't run at all then it is likely you have a bug somewhere, perhaps you used my answer incorrectly. Or maybe something else is going on. And thus I encourage you to ask a question about it here on StackOverflow.Cerate
@Cerate actually I've got work around in my code Looks like that putting await asyncio.sleep(0.0) 'fires' tasks created by create_task functionRebato
@ЯктенсТид every time you use await or when async function exits the control goes back to the event loop which decides what task to run next. Did you do a blocking infinite loop without await inside? Yes, in that scenario, nothing else will run, you simply blocked everything permamently.Cerate
@Cerate actually I am doing some database operations in between. Not sure, maybe because of them it is blockedRebato
@ЯктенсТид well, as I said: feel free to post your code and ask question about it. I'm sure someone will help you with your problem.Cerate
@Cerate well, there is a really big chunk of code, and I am not sure where the problem is exactly) I've tested answer above - it works, if your logic is straightforward and there are just two functions. But my code does not work and it might have plenty of reasons) So I'd better spend my timeRebato
If you use loop.create_task inside of a for loop and add multiple tasks and then add time.sleep(1) (or some other unavoidable blocking function) to the task function, loop.create_task will execute the function calls passed to it in a blocking manner. Is there away around this so that each function call executes separately in an async manner? In my case I have to deal with blocking functions, and I just need the function calls to execute concurrently.Messaline
@Messaline Python's event loop is inherently single threaded. And so any blocking call will block the entire loop. Perhaps you should use threads instead?Cerate
@freakish, I figured out that if I use await loop.run_in_executor(None, time.sleep, 1), as opposed to just time.sleep(1), it will do the job, and I believe run_in_executor is just a wrapper that uses threads from what I've read previously. The function being used with loop.create_task is going to be pretty complicated and I haven't fully built it out, but I think as long as I wrap anything blocking in loop.run_in_executor, it will do the trick.Messaline
@Messaline yes, run_in_executor runs code in a separate thread pool by default. I assume that in reality you have more complicated call than time.sleep(1) right? Cause that can be replaced by await asyncio.sleep(1).Cerate
@freakish, Yes, that's correct. I'm going to have to recursively call a function to check on the status of something until the status gets resolved, so not only do I need async functionality, I probably can't use something like run_until_complete. It needs to be a true "fire and forget" solution. I have more of a JS background, where this is a trivial problem and there's definitely a learning curve with Python.Messaline
Instead of first getting the event loop and calling its create_task method, since Python 3.7 you can just use asyncio.create_task.Straighten
I'm seeing this (fire and forget async logic from async logic) work well with asyncio.create_task or with asyncio.get_running_loop().create_task, but not with a new task. I hope I'm not setting myself up for deadlocks.Cephalic
E
14

In simplest form:

import asyncio
from datetime import datetime

def _log(msg : str):
    print(f"{datetime.utcnow()} {msg}")

async def dummy(name, delay_sec):
    _log(f"{name} entering ...")
    await asyncio.sleep(delay_sec)
    _log(f"{name} done for the day!")

async def main():
    asyncio.create_task(dummy('dummy1', 5)) # last to finish
    asyncio.create_task(dummy('dummy2', 3)) # 2nd
    asyncio.create_task(dummy('dummy3', 1)) # First to finish

    _log(f"Yo I didn't wait for ya!")
    await asyncio.sleep(10)

asyncio.get_event_loop().run_until_complete(main())

Output:

2022-09-18 00:53:13.428285 Yo I didn't wait for ya!
2022-09-18 00:53:13.428285 dummy1 entering ...
2022-09-18 00:53:13.428285 dummy2 entering ...
2022-09-18 00:53:13.428285 dummy3 entering ...
2022-09-18 00:53:14.436801 dummy3 done for the day!
2022-09-18 00:53:16.437226 dummy2 done for the day!
2022-09-18 00:53:18.424755 dummy1 done for the day!
Efficient answered 18/9, 2022 at 0:54 Comment(0)
T
6

Since Python 3.7 this can be easily achieved via asyncio.create_task.

import asyncio

# replace with handler_message or whichever function you want to call
asyncio.create_task(YOUR_ASYNC_FUNCTION(ARG1, ARG2, ETC))

Do note that that the Python docs do additionally say this:

Important

Save a reference to the result of this function, to avoid a task disappearing mid-execution. The event loop only keeps weak references to tasks. A task that isn’t referenced elsewhere may get garbage collected at any time, even before it’s done. For reliable “fire-and-forget” background tasks, gather them in a collection: background_tasks = set()

    task = asyncio.create_task(some_coro(param=i))

    # Add task to the set. This creates a strong reference.
    background_tasks.add(task)

    # To prevent keeping references to finished tasks forever,
    # make each task remove its own reference from the set after
    # completion:
    task.add_done_callback(background_tasks.discard) ```
Torrance answered 7/12, 2023 at 21:6 Comment(0)
D
1

You can call it by multiprocessing pool.

from multiprocessing import Pool
import time

def f(x):
    return x*x

def postprocess(result):
    print("finished: %s" % result)

if __name__ == '__main__':
    pool = Pool(processes=1)              # Start a worker processes.
    result = pool.apply_async(f, [10], callback=postprocess) # Evaluate "f(10)" asynchronously calling callback when finished.
    print("waiting...")
    time.sleep(1)
Deflection answered 14/7, 2023 at 1:58 Comment(1)
IMHO this is a preferred solution In the case when the target function involve a lengthy computation because an asyncio itself can run only one computational block at a time.University
I
0

Problem Statement: I Was Using FastApi on Azure Function App and i was also using Beanie ODM for MongoDB and Beanie is an asynchronous ODM so it need to initialized at the startup event but as azure function are serveless so this cannot be done easily and i cannot also use asyncio.run() because FastAPI is handling it's own async loop to solve this issue it took me 2 days but finally fixed

Here's How I did It , I am not sure about the drawback's of this method but would love to hear if anyone knows that

connection_string = os.environ["DCS"]
logging.info(f"connection string {connection_string}")
async def init():
    client = AsyncIOMotorClient(connection_string)
    await init_beanie(database=client['Tradex'],document_models=[Admin,User])

loop = asyncio.get_running_loop()
asyncio.set_event_loop(loop)
loop.create_task(init())
Iseult answered 6/8, 2023 at 12:59 Comment(0)
P
-2

Other way would be to use ensure_future function:

import asyncio

async def handler_message(request):
...
loop = asyncio.get_event_loop()
loop.ensure_future(perform_message(x,y,z))
...
Purveyance answered 19/6, 2017 at 22:4 Comment(1)
That would raise an AttributeError: '_UnixSelectorEventLoop' object has no attribute 'ensure_future'. ensure_future is a function the asyncio module, not a method of the event loop. It is also essentially the same as loop.create_task if you are only dealing with coroutines.Spendthrift

© 2022 - 2025 — McMap. All rights reserved.