Issue
I generally understand the concept of async vs threads/processes, I am just a bit confused when I am reading about the event loop of asyncio.
When you use asyncio.run()
I presume it creates an event loop? Does this event loop use an executor? The link above says the event loop will use the default executor, which after a brief google search it appears to be ThreadPoolExecutor
.
I am a bit confused if I am writing coroutines why would that use ThreadPoolExecutor
since I don't want to execute my async code in threads.
Solution
When you use
asyncio.run()
I presume it creates an event loop?
Yes, from https://docs.python.org/3/library/asyncio-runner.html#asyncio.run:
This function always creates a new event loop and closes it at the end.
Does this event loop use an executor?
The event loop provides single-threaded concurrency for IO-bound work. For CPU-bound work, which would otherwise block the event loop, asyncio lets you execute code outside the event loop in a separate thread or process, by using an executor.
This is done by calling run_in_executor
. It wraps the executor API and returns an awaitable which lets you use it transparently in async
/await
code, whereas if you used an executor directly, you would have to deal with futures and callbacks.1
The event loop has a default executor which will be used by run_in_executor
if no other executor is provided as argument. A custom default executor can be provided by calling set_default_executor
.
If you don't call run_in_executor
yourself, the code you pass to asyncio.run
will not be executed in an executor, but asyncio might still use the default executor internally for some of its own code, I guess.
asyncio.run
will automatically shut down the default executor at the end by calling shutdown_default_executor
internally (since Python 3.9).2
1 To illustrate the differences, suppose you want to do some CPU-intensive calculation in a separate thread and then report on the result.
Using ThreadPoolExecutor
and Future.add_done_callback
:
from concurrent.futures import ThreadPoolExecutor
from time import sleep
def calculate():
print("Calculating...")
sleep(1)
return 42
def report(future):
print(f"The result is {future.result()}")
with ThreadPoolExecutor() as executor:
future = executor.submit(calculate)
future.add_done_callback(report)
Using ThreadPoolExecutor
and concurrent.futures.as_completed
:
from concurrent.futures import ThreadPoolExecutor, as_completed
from time import sleep
def calculate():
print("Calculating...")
sleep(1)
return 42
with ThreadPoolExecutor() as executor:
futures = [executor.submit(calculate)]
for future in as_completed(futures):
print(f"The result is {future.result()}")
Using asyncio:
import asyncio
from time import sleep
def calculate():
print("Calculating...")
sleep(1)
return 42
async def main():
loop = asyncio.get_event_loop()
result = await loop.run_in_executor(None, calculate)
print(f"The result is {result}")
asyncio.run(main())
2 I'm not sure if the remark about the change in Python 3.9 in https://docs.python.org/3/library/asyncio-runner.html#asyncio.run means that in earlier versions it did not shut down the executor at all, or if it did it by other means than calling shutdown_default_executor
:
This function runs the passed coroutine, taking care of managing [...], and closing the threadpool.
[...]
Changed in version 3.9: Updated to useloop.shutdown_default_executor()
.
Answered By - mkrieger1
0 comments:
Post a Comment
Note: Only a member of this blog may post a comment.