I'm trying to call an async function containing an await function from the main thread without halting computation in the main thread.
I've looked into similar questions employing a variety of solutions two of which I have demonstrated below, however, none of which seem to work with my current setup involving aioconsole
and asyncio
.
Here is a simplified version of what I currently have implemented:
import aioconsole
import asyncio
async def async_input():
line = await aioconsole.ainput(">>> ")
print("You typed: " line)
if __name__ == "__main__":
asyncio.run(async_input())
print("This text should print instantly. It should not wait for you to type something in the console.")
while True:
# Do stuff here
pass
I have also tried replacing
asyncio.run(async_input())
with the code below but that hangs the program even after entering console input.loop = asyncio.new_event_loop() asyncio.set_event_loop(loop) loop.create_task(async_input()) loop.run_forever()
What's currently happening:
- Function is created ✓
- Function is called from the main thread ✓
- The program stops and awaits the completion of the async function. ✗
- The processing that should be performed in parallel with the async function is performed sequentially. ✗
Output
>>>
What should happen:
- Function is created ✓
- Function is called from the main thread ✓
- The program continues while the async function runs in the background. ✓
- The processing following is performed in parallel with the async function. ✓
Expected output
>>>
This text should print instantly. It should not wait for you to type something in the console.
Python version: 3.10.8
Operating System: MacOS Ventura
CodePudding user response:
As pointed out to me by @flakes there were a couple of problems with my approach:
asyncio.run
is a blocking function so wrapping my async function calls in it was preventing any following code from running.- The main content needs to be wrapped in an asynchronous function to give the indented async function time to run.
- The intended async function runs while the main thread is waiting so downtime must be introduced in the main function. This can be easily done with
await asyncio.sleep(x)
.
The introduction of all this fixes the problem while allowing the main content to run synchronously:
import aioconsole
import asyncio
async def async_input():
line = await aioconsole.ainput(">>> ")
print("You typed: " line)
async def main():
input_task = asyncio.create_task(async_input())
print("This text should print instantly. It should not wait for you to type something in the console.")
while True:
# Do stuff here
await asyncio.sleep(0.1)
await input_task
if __name__ == "__main__":
asyncio.run(main())
CodePudding user response:
asyncio.run()
starts and manages an asynchronous event loop and waits until all async job is done. If you need to run some synchronous code concurrently with async coroutines/tasks you need to "embed" that code into the event loop, in particular with asyncio.to_thread
function:
import aioconsole
import asyncio
import time
async def async_input():
line = await aioconsole.ainput(">>> ")
print("You typed: " line)
def func():
print("This text should print instantly. It should not wait for you to type something in the console.")
time.sleep(5)
print('other stuff ...')
async def main():
await asyncio.gather(asyncio.to_thread(func), async_input())
if __name__ == "__main__":
asyncio.run(main())
This text should print instantly. It should not wait for you to type something in the console.
>>> some text
You typed: some text
other stuff ...
Note: Due to the GIL,
asyncio.to_thread()
can typically only be used to make IO-bound functions non-blocking. However, for extension modules that release the GIL or alternative Python implementations that don’t have one,asyncio.to_thread()
can also be used for CPU-bound functions.
The alternative is to use loop.run_in_executor
.
To apply it the main
function needs to be changed to the following:
async def main():
loop = asyncio.get_running_loop()
await asyncio.gather(loop.run_in_executor(None, func), async_input())