More about async
and asyncio
#
Suppose we are writing an Anacreon bot that manages 100 fleets at the same time. Each “fleet manager” may sleep for many minutes, waiting for fleets to get to their destinations before taking action.
Naively, we might write code that looks something like this
import time
def manage_fleet(fleet_id):
while True:
... # do some stuff
time.sleep(60) # wait for next watch
... # do some more stuff
fleets_to_manage = [...]
for fleet in fleets_to_manage:
manage_fleet(fleet)
However, this will take a lot of time to complete. We are for our
“fleet manager” to completely finish managing one fleet before moving on to the
next. The call to time.sleep
stops our whole program. As a result, the
script as written above could take many hours!
Wouldn’t it be great if we could run all of our fleet managers concurrently?
By using coroutines and executing them on the asyncio
event loop, we can
run these concurrently!
import time
import asyncio
async def manage_fleet(fleet_id):
while True:
... # do some stuff
await asyncio.sleep(60)
... # do some more stuff
async def main():
fleets_to_manage = [...]
fleet_manager_tasks = []
# concurrently run each manager on the asyncio event loop
for fleet in fleets_to_manage:
fleet_manager_tasks.append(asyncio.create_task(manage_fleet(fleet)))
# wait for the managers to finish
for manager in fleet_manager_tasks:
await manager
asyncio.run(main())
At a high level, a coroutine is a piece of computation that can be
suspended/resumed. When we call manage_fleet
, it returns a coroutine
object.
>>> manage_fleet(10)
<coroutine object manage_fleet at 0x7fd7c3b3e8c0>
In Python, we can resume these coroutines using their send
method
>>> async def foo():
... print("Hello! I am a coroutine!")
...
>>> coro = foo()
>>> coro.send(None)
Hello! I am a coroutine
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
StopIteration
Because Python coroutines are implemented using generators, when they are done,
they raise StopIteration
, similarly to how generators raise
StopIteration
when they are complete. Coroutines can suspend at await
points, yielding control back to the caller [1].
This is where asyncio
comes in – it provides an event loop [2]
that manages our coroutines, is able to run them concurrently
(using asyncio.create_task()
), and resumes them when they are ready to
be resumed.
This is why anacreonlib
is written with async – to let users easily
write concurrent scripts.