Python - What is the right way to run functions in asyncio? - python

I am using FastApi and have one endpoint.
I have two long running functions which I want to run concurrently using asyncio
Therefore, I have created two functions:
async def get_data_one():
return 'done_one'
async def get_data_two():
return 'done_two'
These functions get data from external webservices.
I want to execute them concurrently so I have created a another function that does it:
async def get_data():
loop = asyncio.get_event_loop()
asyncio.set_event_loop(loop)
task_1 = loop.create_task(get_data_one)
task_2 = loop.create_task(get_data_two)
tasks = (task_1, task_2)
first, second = loop.run_until_complete(asyncio.gather(*tasks))
loop.close()
# I will then perform cpu intensive computation on the results
# for now - assume i am just concatenating the results
return first + second
Finally, I have my endpoint:
#app.post("/result")
async def get_result_async():
r = await get_data()
return r
Even this simple example breaks and I get the following exception when I hit the endpoint:
RuntimeError: This event loop is already running
ERROR: _GatheringFuture exception was never retrieved
future: <_GatheringFuture finished exception=AttributeError("'function' object has no attribute 'send'",)>
AttributeError: 'function' object has no attribute 'send'
This is a simplified code but I would really appreciate how to do it the right way.

When in FastAPI context, you never need to run an asyncio loop; it's always running for as long as your server process lives.
Therefore, all you need is
import asyncio
async def get_data_one():
return "done_one"
async def get_data_two():
return "done_two"
async def get_data():
a, b = await asyncio.gather(get_data_one(), get_data_two())
return a + b
##route decorator here...
async def get_result_async():
r = await get_data()
print("Get_data said:", r)
return r
# (this is approximately what is done under the hood,
# presented here to make this a self-contained example)
asyncio.run(get_result_async())

It's as simple as:
async def get_data():
first, second = await asyncio.gather(
get_data_one(),
get_data_two(),
)
return first + second

Related

asyncio.run() cannot be called from a running event loop in FastAPI

In an API developed using FastAPI framework, I am using asyncio to make calls to solr collections, but when I am calling asyncio.run(man(a,b)) from a python file query.py then I am getting asyncio.run() cannot be called from a running event loop.
in controller.py
#router.api_route()
async def api_call(a,b):
#calling a function
resp = query(a,b)
return resp
in query.py
def query(a,b):
result = asyncio.run(man(a,b))
return result
in database_call.py
async def man(a,b)
async with aiohttp.ClientSession() as session:
url = ''
async with session.get(pokemon_url) as resp:
result = await resp.json()
return result
when I am calling asyncio.run(man(a,b)) from query then I am getting asyncio.run() cannot be called from a running event loop. Kindly help me resolve the issue.
I tried:
in query.py
def query(a,b):
loop = asyncio.get_event_loop
result = loop.create_task(man(a,b))
return result
then I am getting <coroutine object main at 0x0394999ejt>
The docs say that you should have only one call to asyncio.run in a program. "Should" doesn't mean the same thing as "must", so it's not a requirement. But it's a good guideline.
Solution 1: get rid of query entirely, and just await the coroutine man() directly.
#router.api_route()
async def api_call(a,b):
return await man(a, b)
Solution 2: declare query to be an async def function, and await it:
#router.api_route()
async def api_call(a,b):
#calling a function
return await query(a,b)
async def query(a,b):
return await man(a,b)
Solution 3: Do not declare query to be async def, but have it return an awaitable object. This is similar to what you tried in your last listing, but you need to await the result.
#router.api_route()
async def api_call(a,b):
#calling a function
return await query(a,b)
def query(a, b):
return asyncio.create_task(man(a, b))
Solution 4: run query in another thread using the asyncio.to_thread function.
Solution 5: run query in a ThreadPool or ProcessPool.
The whole idea of asyncio (or any other form of parallel processing) is to allow the use of a task or a coroutine that does not finish immediately. It runs at a later time, and its result is not available until enough time has passed. Whether you use threading, multiprocessing or asyncio the situation is the same. If you need the answer from the coroutine/task/function in order for your program to continue, you need to wait for it somehow.

Pass in a list of Async API calls to an indefinite loop

I'm trying to work out how I can execute async function calls in an indefinite loop where I don't know what the API calls will look like.
So for example: If I define 5 HTTP calls which are then parsed by the application and then the 5 api calls are should be made asynchronously using the aiohttp library in an indefinite loop.
I currently have the following piece of code which performs the asynchronous API calls indefinitely, but I was wondering if there was a more efficient way to go about things:
Generate a list of async function calls to loop through based on user input:
def get_list(self):
request_list = []
while i < num_calls:
request_list.append(self.api_request(self.PROMETHEUS_URL)
i++
return request_list
The request_list from step 1 is passed into the asyncio.gather() function. It then concurrently executes the async functions from step 1 within an indefinite loop.
async def call_requests(self):
while True:
timer_start = time.perf_counter()
await asyncio.gather(*self.get_list())
timer_stop = time.perf_counter()
print(f"Performed all requests in... {timer_stop - timer_start:0.4f} seconds")
The async function that are passed in to asyncio.gather() is the following:
async def api_request(self, query, sleep_duration):
await asyncio.sleep(sleep_duration)
async with ClientSession() as session:
async with session.get(self.PROMETHEUS_URL) as response:
response = await response.text()
random_int = int(response[2])
print('Response value... {0}'.format(random_int))
My questions are:
Is there a more efficient way of indefinitely performing async api calls?
Are there any issues with the code above? It works on my machine.

python3.6 async/await still works synchronously with fastAPI

I have a fastAPI app that posts two requests, one of them is longer (if it helps, they're Elasticsearch queries and I'm using the AsyncElasticsearch module which already returns coroutine). This is my attempt:
class my_module:
search_object = AsyncElasticsearch(url, port)
async def do_things(self):
resp1 = await search_object.search() #the longer one
print(check_resp1)
resp2 = await search_object.search() #the shorter one
print(check_resp2)
process(resp2)
process(resp1)
do_synchronous_things()
return thing
app = FastAPI()
#app.post("/")
async def service(user_input):
result = await my_module.do_things()
return results
What I observed is instead of awaiting resp1, by the time it got to check_resp1 it's already a full response, as if I didn't use async at all.
I'm new to python async, I knew my code wouldn't work, but I don't know how to fix it. As far as I understand, when interpreter sees await it starts the function then just moves on, which in this case should immediately post the next request. How do I make it do that?
Yes, that's correct the coroutine won't proceed until the results are ready. You can use asyncio.gather to run tasks concurrently:
import asyncio
async def task(msg):
print(f"START {msg}")
await asyncio.sleep(1)
print(f"END {msg}")
return msg
async def main():
await task("1")
await task("2")
results = await asyncio.gather(task("3"), task("4"))
print(results)
if __name__ == "__main__":
asyncio.run(main())
Test:
$ python test.py
START 1
END 1
START 2
END 2
START 3
START 4
END 3
END 4
['3', '4']
Alternatively you can use asyncio.as_completed to get the earliest next result:
for coro in asyncio.as_completed((task("5"), task("6"))):
earliest_result = await coro
print(earliest_result)
Update Fri 2 Apr 09:25:33 UTC 2021:
asyncio.run is available since Python 3.7+, in previous versions you will have to create and start the loop manually:
if __name__ == "__main__":
loop = asyncio.get_event_loop()
loop.run_until_complete(main())
loop.close()
Explanation
The reason your code run synchronyously is that in do_things function, the code is executed as follow:
Schedule search_object.search() to execute
Wait till search_object.search() is finished and get the result
Schedule search_object.search() to execute
Wait till search_object.search() is finished and get the result
Execute (synchronyously) process(resp2)
Execute (synchronyously) process(resp1)
Execute (synchronyously) do_synchronous_things()
What you intended, is to make steps 1 and 3 executed before 2 and 4. You can make it easily with unsync library - here is the documentation.
How you can fix this
from unsync import unsync
class my_module:
search_object = AsyncElasticsearch(url, port)
#unsync
async def search1():
return await search_object.search()
#unsync
async def search2(): # not sure if this is any different to search1
return await search_object.search()
async def do_things(self):
task1, task2 = self.search1(), self.search2() # schedule tasks
resp1, resp2 = task1.result(), task2.result() # wait till tasks are executed
# you might also do similar trick with process function to run process(resp2) and process(resp1) concurrently
process(resp2)
process(resp1)
do_synchronous_things() # if this does not rely on resp1 and resp2 it might also be put into separate task to make the computation quicker. To do this use #unsync(cpu_bound=True) decorator
return thing
app = FastAPI()
#app.post("/")
async def service(user_input):
result = await my_module.do_things()
return results
More information
If you want to learn more about asyncio and asyncronyous programming, I recommend this tutorial. There is also similar case that you presented with a few possible solutions to make the coroutines run concurrently.
PS. Obviosuly I could not run this code, so you must debug it on your own.

How to use asyncio.run_coroutine_threadsafe correctly?

In short, the problem is that the future returned by asyncio.run_coroutine_threadsafe is blocking when I call future.result()
The problem is also documented in the following question with (currently) no satisfactory answer: Future from asyncio.run_coroutine_threadsafe hangs forever?
What I'm trying to achieve is to call async code from sync code, where the sync code is actually itself wrapped in async code with an existing running event loop (to make things more concrete: it's a Jupyter notebook).
I would want to send async tasks from nested sync code to the existing 'outer' event loop and 'await' its results within the nested sync code. Implied constraint: I do not want to run those tasks on a new event loop (multiple reasons).
Since it's not possible to just 'await' an async result from sync code without blocking and without using asyncio.run which creates a new event loop, I thought using a separate thread would somehow help.
From the documentation description, asyncio.run_coroutine_threadsafe sounds like the perfect candidate.
But it's still blocking...
Bellow full snippet, with a timeout when calling the future's result.
How can I get this code to work correctly?
import asyncio
from concurrent.futures import ThreadPoolExecutor
async def gather_coroutines(*coroutines):
return await asyncio.gather(*coroutines)
def run_th_safe(loop, coroutines):
future = asyncio.run_coroutine_threadsafe(gather_coroutines(*coroutines), loop)
res = future.result(timeout=3) # **** BLOCKING *****
return res
def async2sync(*coroutines):
try:
loop = asyncio.get_running_loop()
except RuntimeError:
return asyncio.run(gather_coroutines(*coroutines))
# BLOW DOESN'T WORK BECAUSE run_th_safe IS BLOCKING
with ThreadPoolExecutor(max_workers=1) as ex:
thread_future = ex.submit(run_th_safe, loop, coroutines)
return thread_future.result()
# Testing
async def some_async_task(n):
"""Some async function to test"""
print('Task running with n =', n)
await asyncio.sleep(n/10)
print('Inside coro', n)
return list(range(n))
async def main_async():
coro3 = some_async_task(30)
coro1 = some_async_task(10)
coro2 = some_async_task(20)
results = async2sync(coro3, coro1, coro2)
return results
def main_sync():
coro3 = some_async_task(30)
coro1 = some_async_task(10)
coro2 = some_async_task(20)
results = async2sync(coro3, coro1, coro2)
return results
if __name__ == '__main__':
# Testing functionnality with asyncio.run()
# This works
print(main_sync())
# Testing functionnality with outer-loop (asyncio.run) and nested asyncio.run_coroutine_threadsafe
# **DOESN'T WORK**
print(asyncio.run(main_async()))

Sync code to async, without rewriting the function

Basically I have synchronous code something similar to this:
def f(.):
...
def call(.):
..Some sync code..
try:
resp = f(..)
...some more transformations of resp...
return ..
except:
..Some error handling..
async def af(.):
...
Basically I want to dynamically alter the code of call so that it can call and await af function instead of f. Is there a way around this? I have found syncit on github, but it doesn't seem to be a solution to me as you have to rewerite the code to async first and then downgrade it to sync. Any help will be appreciated.
In asyncio world every coroutine can be executied either inside other coroutine (using await) or by event loop blocking call (using run_until_complete()).
You can't await coroutine inside regular function since this function's call would be blocking, while coroutine to be awaited needs blocking event loop execution. This is how asyncio designed to work.
If you ok with idea of your call() function being blocking and have access to f() implementation, you can execute coroutine inside f() runnning event loop:
async def af(.):
...
def f(.):
loop = asyncio.get_event_loop()
return loop.run_until_complete(af())
def call(.):
..Some sync code..
try:
resp = f(..)
...some more transformations of resp...
return ..
except:
..Some error handling..
If you don't have access to f() implementation, I don't believe you would be able to alter call() to await coroutines (without some ugly monkey-patching).
I think rewriting call() to be async would be only good option then.
Here are two options mixing synchronous and asynchronous code.
Given
import asyncio
import trio
from unsync import unsync
Code
Option 1 - trio
Given sync code (call), call an async function via trio (async_div):
# Sync code
def call(x, y):
"""Return an awaited result."""
try:
resp = async_div(x, y)
except ZeroDivisionError:
return "Caught an exception."
return resp
# Wrapper
def async_div(*args):
"""Return results from an async function."""
return trio.run(_async_div, *args)
# Async code
async def _async_div(x, y):
"""Return an async result."""
await trio.sleep(3)
return x / y
Option 2 - unsync
Decorate the async code with #unsync and call the result():
# Sync code
def call(x, y):
"""Return an awaited result."""
try:
resp = async_div(x, y).result()
except ZeroDivisionError:
return "Caught an exception."
return resp
# Async code
#unsync
async def async_div(x, y):
"""Return an async result."""
await asyncio.sleep(3)
return x / y
Demo
After the prescribed delay (3 secs), the results are the same:
call(0, 1)
# 0.0
call(1, 0)
# 'Caught an exception.'
See Also
Some additional resources:
docs on trio
Talk Python interview with trio creator N. Smith for more details and his philosophy on asynchronous programming.
Python Bytes episode 73, item 6 on quick unsync details
M. Kennedy's webinar demo on using unsync to speed up requests

Categories