I have a fastapi endpoint that calls a python script but has two problems on GCP:
it always give a success code (because it's not blocking)
The instances is always running as cloud rundoesn't know when to turn it off(because it's not blocking).
I think the problem is blocking :-)
Here's a sample of my code:
async def curve_builder(secret: str):
os.system("python3 scripts/my_script.py")
return {"succcess": True, "status": "Batch job completed"}
Is there a way to let the script and then return a success/fail message once it's done? I'm not sure how to block it, it seems to just return a success as soon as the command is executed.
I'm not sure if this is specific to fastapi or general python.
Blocking operations could hang up your current worker. When you want to execute blocking code over a coroutine, send its logic to a executor.
Get the event loop
loop = asyncio.get_running_loop()
Any blocking code must go out of your coroutine. So, your current worker will be able to execute other coroutines.
await loop.run_in_executor(None, func)
For your case, the final result will be:
async def curve_builder(secret: str):
loop = asyncio.get_running_loop()
result = await loop.run_in_executor(None, lambda: os.system("python3 scripts/my_script.py"))
return {"status": result}
You can read further information in the docs: https://docs.python.org/3/library/asyncio-eventloop.html#asyncio.loop.run_in_executor
Assign the ‘os.system()’ call to a variable. The exit code of your script is assigned, so it will wait till it finished, despite being a async method you are working from.
The answer was wrong, I've tested an example setup but could not reproduce the issue.
Script1:
import os
import asyncio
async def method1():
print("Start of method1")
os.system("python /path/to/other/script/script2.py")
print("End of method1")
print("start script1")
asyncio.run(method1())
print("end script1")
Script2:
import asyncio
async def method2():
print("Start method2")
await asyncio.sleep(3)
print("End method2")
print("start async script2")
asyncio.run(method2())
print("end async script2")
Output:
start script1
Start of method1
start async script2
Start method2
End method2
End async script2
End of method
end script1
Related
I'm trying to run some code asynchronously. My expectation is that the test coroutine should not block the print(running first) statement. This is because I've dispatched it to the event loop, and should be seeing the output of this command logged first.
import asyncio
async def test():
await asyncio.sleep(5)
print("I should run second")
asyncio.run(test())
print('running first')
Does anyone have any tips on how to how this code run so that print('running first') is ran before print("I should run second")? I believe this code should be non-blocking, so I'm confused as to why the order of print messages isn't matching my expectation.
I believe this is what you want:
import asyncio
async def test():
await asyncio.sleep(5)
print("I should run second")
async def main():
task1 = asyncio.create_task(test())
print('running first')
await task1
asyncio.run(main())
A more detail explaination:
asyncio.run() will try to wait all of the task inside it to finish before it continues.
In your code, you are running asyncio.run(test()) first and it will continue ONLY IF test() IS ENDED and you awaited the sleep. so test() will end after the sleep and run the print then the main print.
This is why your code delay so long before running. The solution to it is simple. Fire the task without waiting, which is what asyncio.create_task() is doing, I created a task, fire it but wait it at the end.
btw normally when you are using async, you will have a ton of task in a list. If you want to wait it as a list you should use gather():
import asyncio
async def test():
await asyncio.sleep(5)
print("I should run second")
async def main():
task_list = []
for _ in range(100):
task_list.append(asyncio.create_task(test()))
print('running first')
await asyncio.gather(*task_list)
asyncio.run(main())
I am trying to do something similar like C# ManualResetEvent but in Python.
I have attempted to do it in python but doesn't seem to work.
import asyncio
cond = asyncio.Condition()
async def main():
some_method()
cond.notify()
async def some_method():
print("Starting...")
await cond.acquire()
await cond.wait()
cond.release()
print("Finshed...")
main()
I want the some_method to start then wait until signaled to start again.
This code is not complete, first of all you need to use asyncio.run() to bootstrap the event loop - this is why your code is not running at all.
Secondly, some_method() never actually starts. You need to asynchronously start some_method() using asyncio.create_task(). When you call an "async def function" (the more correct term is coroutinefunction) it returns a coroutine object, this object needs to be driven by the event loop either by you awaiting it or using the before-mentioned function.
Your code should look more like this:
import asyncio
async def main():
cond = asyncio.Condition()
t = asyncio.create_task(some_method(cond))
# The event loop hasn't had any time to start the task
# until you await again. Sleeping for 0 seconds will let
# the event loop start the task before continuing.
await asyncio.sleep(0)
cond.notify()
# You should never really "fire and forget" tasks,
# the same way you never do with threading. Wait for
# it to complete before returning:
await t
async def some_method(cond):
print("Starting...")
await cond.acquire()
await cond.wait()
cond.release()
print("Finshed...")
asyncio.run(main())
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.
I'm new to Python and have code similar to the following:
import time
import asyncio
async def my_async_function(i):
print("My function {}".format(i))
async def start():
requests = []
# Create multiple requests
for i in range(5):
print("Creating request #{}".format(i))
requests.append(my_async_function(i))
# Do some additional work here
print("Begin sleep")
time.sleep(10)
print("End sleep")
# Wait for all requests to finish
return await asyncio.gather(*requests)
asyncio.run(start())
No matter how long the "additional work" takes, the requests seem to only run after "End sleep". I'm guessing asyncio.gather is what actually begins to execute them. How can I have the requests (aka my_async_function()) start immediately, do additional work, and then wait for all to complete at the end?
Edit:
Per Krumelur's comments and my own findings, the following results in what I'm looking for:
import time
import asyncio
import random
async def my_async_function(i):
print("Begin function {}".format(i))
await asyncio.sleep(int(random.random() * 10))
print("End function {}".format(i))
async def start():
requests = []
# Create multiple requests
for i in range(10):
print("Creating request #{}".format(i))
requests.append(asyncio.create_task(my_async_function(i)))
# Do some additional work here
print("Begin sleep")
await asyncio.sleep(5)
print("End sleep")
# Wait for all requests to finish
return await asyncio.gather(*requests)
asyncio.run(start())
This only works if my_async_function and the "additional work" both are awaitable so that the event loop can give each of them execution time. You need create_task (if you know it's a coroutine) or ensure_future (if it could be a coroutine or future) to allow the requests to run immediately, otherwise they still end up running only when you gather.
time.sleep() is a synchronous operation
You’ll want to use the asynchronous sleep and await it,
E.g.
await asyncio.sleep(10)
Other async code will only run when the current task yields (I.e. typically when “await”ing something).
Using async code means you have to keep using async everywhere. Async operations are meant for I/O-bound applications. If “additional work” is mainly CPU-bound, you are better off using threads (but beware the global interpreter lock!)
As I read more, I feel more stupid about aysnc in python. So I decided to ask for a direct answer. How can I change the following code (using async or similar approaches) to achieve the desired result? Additionally, how can I do it in flask or sanic?
import time
def long_job():
print('long job started')
time.sleep(5)
print('long job ended')
def main_job():
long_job()
time.sleep(1)
print('main job returned')
main_job()
# expected result:
# 'long job started'
# 'main job returned'
# 'long job ended'
Basically, I do NOT want to await for long_job to end before returning my main_job. Thank you in advance. :)
Await asyncio's sleep() to yield time to other jobs (if you don't need to await something else).
Use create_task() instead of await to start a job without blocking.
Finally, you have to start the main job using the event loop.
# Written in Python 3.7
import asyncio
async def long_job():
print('long job started')
await asyncio.sleep(5)
print('long job ended')
async def main_job():
asyncio.create_task(long_job())
await asyncio.sleep(1)
print('main job returned')
Your framework should start the event loop, you don't have to start it yourself. You can await or call create_task on main_job() from an async def function called by your framework, depending on if you want to block or not.
If you want to test this without a framework, you'll have to start the loop yourself using asyncio.run(). This will stop immediately after its task completes, even if other tasks haven't finished yet. But this is easy enough to work around:
async def loop_job():
asyncio.create_task(main_job())
while len(asyncio.Task.all_tasks()) > 1: # Any task besides loop_job()?
await asyncio.sleep(0.2)
asyncio.run(loop_job())
If you're implementing a framework yourself, you can use the more primitive loop.run_forever(), but you'd have to stop() it yourself.