Coroutines with async and await
-
Coroutines can be declared with
asyncandawaitsyntax- This is the preferred way of writing asyncio applications
-
The difference between the two is the following:
async def:Creates a coroutineawait:Awaits an awaitable object
-
The following are considered awaitable objects:
- Python coroutines
- asyncio
Tasks - asyncio
Futures
- In asyncio, coroutines can be run with the
run()function - In asyncio, coroutines can be run concurrently as
Tasks - A task can be created using the
create_task()function -
A task usually involves the following:
- Define a task by wrapping a coroutine
-
Make sure task is defined in a coroutine function
- Meaning, we define a task in a coroutine function
- We do this by defining a task in an
async def
- Run coroutine function using
run()
Differentiating between Coroutines and Tasks
>>> from asyncio import sleep, create_task, run
>>> import time
>>> async def sleepy_coro():
... start = time.time()
... await sleep(10)
... await sleep(10)
... print('coro: ' + str(round(time.time()-start)))
>>> async def sleepy_task():
... t1 = create_task(sleep(10))
... t2 = create_task(sleep(10))
... start = time.time()
... await t1
... await t2
... print('task: ' + str(round(time.time()-start)))
>>> run(sleepy_coro())
'coro: 20'
>>> run(sleepy_task())
'task: 10'Defining Awaitables
- An object is an awaitable if it uses an
awaitexpression -
There are three main types of awaitable objects:
- Python coroutines
- asyncio
Tasks - asyncio
Futures
- Since Python coroutines are awaitables, they can be awaited from other coroutines
Tasksare used to schedule coroutines concurrently- Again, a coroutine is wrapped into a
Taskusingcreate_task - At a high level, a coroutine will wait until a
Futureis resolved - Normally, there is no need to create
Futureobjects ourselves
Describing run(coro)
- This function executes a coroutine
coro - This function runs the passed coroutine
coro - This function always creates a new event loop and closes it at the end
-
In other words, this function will always take care of:
- Managing the asyncio event loop
- Finalizing asynchronous generators
- This function can't be called when another asyncio event loop is running in the same thread
Describing create_task(coro)
- This function wraps a coroutine
corointo aTaskobject - Then, it schedules the execution for this
Task - It returns the
Taskinstance
Describing sleep(delay)
- This function blocks I/O for
delayseconds - This function always suspends the current task
- Meaning, other tasks will be able to run
Describing gather(*aws)
- This function runs awaitable objects
*awsconcurrently - If any awaitable object in
*awsis a coroutine, it is automatically scheduled as aTask - If all awaitables are completed successfully, then this function returns a list of values
- The order of result values corresponds to the order of awaitables in
*aws - If
gatheris cancelled, then all submitted awaitables are also cancelled
Example of gather
>>> import asyncio
>>> async def factorial(name, num):
... f = 1
... for i in range(2, num+1):
... print('Task {name}: Calc fac({i})')
... await asyncio.sleep(1)
... f *= i
... print('Task {name}: fac({num}) = {f}')
>>> async def main():
... # Schedule 3 calls concurrently
... await asyncio.gather(
... factorial('a', 2),
... factorial('b', 3),
... factorial('c', 4),
... )
>>> asyncio.run(main())
'Task a: Calc fac(2)'
'Task b: Calc fac(2)'
'Task c: Calc fac(2)'
'Task a: fac(2) = 2'
'Task b: Calc fac(3)'
'Task c: Calc fac(3)'
'Task b: fac(3) = 6'
'Task c: Calc fac(4)'
'Task c: fac(4) = 24'References
Previous
Next