Home > Blockchain >  How to call an async function from the main thread without waiting for the function to finish (utili
How to call an async function from the main thread without waiting for the function to finish (utili

Time:01-22

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:

  1. Function is created ✓
  2. Function is called from the main thread ✓
  3. The program stops and awaits the completion of the async function. ✗
  4. The processing that should be performed in parallel with the async function is performed sequentially. ✗

Output

>>>

What should happen:

  1. Function is created ✓
  2. Function is called from the main thread ✓
  3. The program continues while the async function runs in the background. ✓
  4. 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:

  1. asyncio.run is a blocking function so wrapping my async function calls in it was preventing any following code from running.
  2. The main content needs to be wrapped in an asynchronous function to give the indented async function time to run.
  3. 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())
  • Related