async function in Python not working as expected - python

I have the following code snippet:
import asyncio
def main():
asyncio.run(work())
print("BEFORE")
async def work():
await asyncio.sleep(1)
print("AFTER")
main()
I would like "BEFORE" to be printed first, followed by "AFTER" however this prints "AFTER" first instead - have I misunderstood how async functions work? I thought that running asyncio.run(...) would allow my code to skip to print("BEFORE") while still running work() in the background.
Ideal output is:
BEFORE
AFTER
Right now it's
AFTER
BEFORE

run seems to be blocking, so you might want to just use gather here:
import asyncio
async def main():
await asyncio.gather(work(), setup())
print('After gather')
async def work():
await asyncio.sleep(2)
print("AFTER")
async def setup():
print("BEFORE")
asyncio.run(main())
This correctly prints
BEFORE
AFTER
After gather
The entire gather statement itself is blocking as well, but all of the functions specified in the gather statement can run asynchronously.

According to the documentation:
The asyncio.run() function to run the top-level entry point “main()” function (see the above example.)
The asyncio.run should be the entry point, not used for starting a background job. So it does not skip to print("BEFORE").

Related

accesing loop attribute in non-async contexts

async def create_db_pool():
database_url = ''
client.pg_con = await asyncpg.create_pool(database_url, ssl="require")
so i have this function in my discord bot code but when i try to run this code using
client.loop.run_until_complete(create_db_pool())
i get the following error currently i am looking for a workaround for this or any way to solve it
AttributeError: loop attribute cannot be accessed in non-async contexts. Consider using either an asynchronous main function and passing it to asyncio.run or using asynchronous initialisation hooks such as Client.setup_hook
You must be using the master version of discord.py
It recently introduced breaking changes with asyncio, with one of them being this.
client.loop is no more accessible in a sync context. This gist explains what was the change and how to make a work around.
First way would be to introduce a setup_hook() function inside a commands.Bot subclass and use await create_db_pool() in there
class MyBot(commands.Bot):
def __init__(self, **kwargs):
super().__init__(**kwarg)
self.pg_conn: = None
async def create_db_pool(self): # making it a bound function in my example
database_url = ''
self.pg_con = await asyncpg.create_pool(database_url, ssl="require")
async def setup_hook(self):
await self.create_db_pool() # no need to use `loop.run_*` here, you are inside an async function
or you could also do this inside a main() function
async def main():
await create_db_pool() # again, no need to run with AbstractLoopEvent if you can await
await bot.start(TOKEN)
asyncio.run(main())
Are you running your bot in a synchronous context? The code should look something like:
async def on_message(ctx): ...
Also please show some code. But I think learning the asyncio module will help. Anyway, try this:
import asyncio
async def create_db_pool() -> None: ... # Your code
loop = asyncio.get_event_loop()
loop.run_until_complete(function_async())
loop.close()
This will not run your function asynchronously, but it doesn't seem like you wish to do so. But it should actually successfully run the function.

Why does 'await' break from the local function when called from main()?

I am new to asynchronous programming, and while I understand most concepts, there is one relating to the inner runnings of 'await' that I don't quite understand.
Consider the following:
import asyncio
async def foo():
print('start fetching')
await asyncio.sleep(2)
print('done fetcihng')
async def main():
task1 = asyncio.create_task(foo())
asyncio.run(main())
Output: start fetching
vs.
async def foo():
print('start fetching')
print('done fetcihng')
async def main():
task1 = asyncio.create_task(foo())
asyncio.run(main())
Output: start fetching followed by done fetching
Perhaps it is my understanding of await, which I do understand insofar that we can use it to pause (2 seconds in the case above), or await for functions to fully finish running before any further code is run.
But for the first example above, why does await cause 'done fetching' to not run??
asyncio.create_task schedules an awaitable on the event loop and returns immediately, so you are actually exiting the main function (and closing the event loop) before the task is able to finish
you need to change main to either
async def main():
task1 = asyncio.create_task(foo())
await task1
or
async def main():
await foo()
creating a task first (the former) is useful in many cases, but they all involve situations where the event loop will outlast the task, e.g. a long running server, otherwise you should just await the coroutine directly like the latter

Threading async function python

Im trying to multithread async functions, one with input, could you please help me?
functions
#client.event
async def on_message(message):
print(f"{message.author.name}: {message.content}")
#client.event
async def on_ready():
while True:
message=input("> ")
run code:
if __name__ == "__main__":
try:
client.run(token, bot=True)
except:
exit(1)
off topic: the default value for bot is true, so you can just do client.run(token)
on topic: async "replaces" threading, https://stackoverflow.com/a/27265877/10192011
also if you want to have a input in discord.py you should look into tasks
Very late to the party but thought I'd share for any future readers.
Sync programming is where python goes line by line and executes every statement in order, while async programming is when you define multiple blocks of code and you set them up to run at a later time when the CPU has spare cycles.
The issue here is that you can run sync functions in an async context, and those lengthy sync tasks will block the thread, stopping also all other async tasks (hence their "blocking tasks" name).
input() is a synchronous blocking task that sits idly waiting for user input until the enter key is pressed, but since it is a blocking function it will stop all other asynchronous code, including discord.py.
What you could do is make use of a separate package, aioconsole, which, among other cool things, includes aioconsole.ainput(). This is entirely similar to input() but works asynchronously:
from aioconsole import ainput
#client.event
async def on_ready():
while True:
message = await ainput("> ")
# do whatever
Do note that the prompt you pass to ainpput() will be printed at the very beginning, if something else (due to this being an asynchronous context) prints while the user hasn't pressed Enter yet, the prompt message will stay where it is (including anything the user has typed so far) and the new printed text added after it, which may result to very confusing interactions...
Using a non async def as the thread target and run the async def from the thread target is one of the solution. Like below.
Example:
async def log_pub(self):
while 1:
sleep(1)
await self.Publish()
#Break the loop based on event
if thread_exit_event.is_set():
break
def handleThread(self):
asyncio.run(self.log_pub())
def startLogging(self):
thread = threading.Thread(target=self.handleThread, args=())
thread.start()

How to use python async def without awaiting on every parent function call

I was wondering if it possible to create async function without setting main as async.
I have a function call like this:
async def C:
t = asyncio.create_subprocess_shell(command)
...
await t
def B:
asyncio.set_event_loop(asyncio.new_event_loop())
C()
<How to await for C?>
def A:
B()
I'm unable to await for C (either as a task or a future). The function exits immediately after starting C(). I've tried loop.create_task and loop.run_until_complete(task) but nothing seems to work.
I don't want to set all parent function calls as async up to main(). Is there a way to do this?
Edit:
My original problem is to run multiple shell commands in parallel (which start another application) from a python function and then wait for their results.
The canonical way to use asyncio in modern Python (3.7 and later) is something like:
async def main():
... your code here ...
async.run(main())
To do multiple things in parallel, you can use asyncio.gather. For example:
async def main():
proc1 = await asyncio.create_subprocess_shell(command1)
proc2 = await asyncio.create_subprocess_shell(command2)
# wait in parallel for both processes to finish and obtain their statuses
status1, status2 = await asyncio.gather(proc1.wait(), proc2.wait())

Removing async pollution from Python

How do I remove the async-everywhere insanity in a program like this?
import asyncio
async def async_coro():
await asyncio.sleep(1)
async def sync_func_1():
# This is blocking and synchronous
await async_coro()
async def sync_func_2():
# This is blocking and synchronous
await sync_func_1()
if __name__ == "__main__":
# Async pollution goes all the way to __main__
asyncio.run(sync_func_2())
I need to have 3 async markers and asyncio.run at the top level just to call one async function. I assume I'm doing something wrong - how can I clean up this code to make it use async less?
FWIW, I'm interested mostly because I'm writing an API using asyncio and I don't want my users to have to think too much about whether their functions need to be def or async def depending on whether they're using a async part of the API or not.
After some research, one answer is to manually manage the event loop:
import asyncio
async def async_coro():
await asyncio.sleep(1)
def sync_func_1():
# This is blocking and synchronous
loop = asyncio.get_event_loop()
coro = async_coro()
loop.run_until_complete(coro)
def sync_func_2():
# This is blocking and synchronous
sync_func_1()
if __name__ == "__main__":
# No more async pollution
sync_func_2()
If you must do that, I would recommend an approach like this:
import asyncio, threading
async def async_coro():
await asyncio.sleep(1)
_loop = asyncio.new_event_loop()
threading.Thread(target=_loop.run_forever, daemon=True).start()
def sync_func_1():
# This is blocking and synchronous
return asyncio.run_coroutine_threadsafe(async_coro(), _loop).result()
def sync_func_2():
# This is blocking and synchronous
sync_func_1()
if __name__ == "__main__":
sync_func_2()
The advantage of this approach compared to one where sync functions run the event loop is that it supports nesting of sync functions. It also only runs a single event loop, so that if the underlying library wants to set up e.g. a background task for monitoring or such, it will work continuously rather than being spawned each time anew.

Categories

Resources