Running function in IOLoop.current().run_in_executor?

Viewed 87

I read the following code

async def f():
    sc_client = session.client("ec2")
    for id in ids:
        await IOLoop.current().run_in_executor(None, lambda: client.terminate(id))

How does it compare to the following code? Will client.terminate be run in parallel? But each execution is awaited?

    for id in ids:
        client.terminate(id)
1 Answers

Will client.terminate be run in parallel?

NO, it still runs as sequence.

IOLoop.current().run_in_executor will run the blocking function in a separate thread and returns an asyncio.Future, while await will wait until the Future which call client.terminate finish, then the loop continue.

The difference the 2 options you given is:

If the program has other coroutine to run, using the 1st option, the other coroutine won't block, while using the 2nd option, the other coroutine will block to wait your for loop finish.

An example to make you understand it (Here, will use loop.run_in_executor to simulate the IOLoop.current().run_in_executor for simple sake):

test.py:

import asyncio
import concurrent.futures
import time

def client_terminate(id):
    print(f"start terminate {id}")
    time.sleep(5)
    print(f"end terminate {id}")

async def f():
    loop = asyncio.get_running_loop()

    for id in range(2):
        with concurrent.futures.ThreadPoolExecutor() as pool:
            await loop.run_in_executor(pool, client_terminate, id)
        # client_terminate(id)

async def f2():
    await asyncio.sleep(1)
    print("other task")

async def main():
    await asyncio.gather(*[f(), f2()])

asyncio.run(main())

The run output is:

$ python3 test.py
start terminate 0
other task
end terminate 0
start terminate 1
end terminate 1

You could see the two client_terminate in for loop still runs in sequence, BUT, the function f2 which print other task inject between them, it won't block asyncio scheduler to schedule f2.

Additional:

If you comment the 2 lines related to await loop.run_in_executor & threadpool, directly call client_terminate(id), the output will be:

$ python3 test.py
start terminate 0
end terminate 0
start terminate 1
end terminate 1
other task

Means if you don't wraps the blocking function in a Future, the other task will have to wait your for loop to finish which waste CPU.

Related