14.4. Async Coroutine
Coroutine function and coroutine object are two different things
Coroutine function is the definition (
async def
)Coroutine function will create coroutine when called
This is similar to generator object and generator function
Coroutine functions are not awaitables
Coroutine objects are awaitables
Coroutines declared with the
async
/await
syntax is the preferred way of writing asyncio applications. [2]
In Python, a coroutine is a special type of function that allows for
asynchronous programming. Coroutines are defined using the async def
syntax and can be paused and resumed at specific points using the await
keyword.
When a coroutine is called, it returns a coroutine object, which is a type of generator object. The coroutine object can be used to control the execution of the coroutine, allowing it to be paused and resumed at specific points.
Here is an example of a simple coroutine in Python:
>>> async def my_coroutine():
... print("Coroutine started.")
... await asyncio.sleep(0.5)
... print("Coroutine resumed.")
... await asyncio.sleep(1.0)
... print("Coroutine complete.")
In this example, the my_coroutine
function is defined using the
async def
syntax. It prints a message, sleeps for 0.5 second using
the await
keyword and the asyncio.sleep()
function, prints another
message, sleeps for an additional 1.0 seconds using the await
keyword
and the asyncio.sleep()
function, and then prints a final message.
To call the coroutine, you can use the await
keyword:
>>> await my_coroutine()
When the coroutine is called using await
, it will execute until it reaches
the first await
statement, which will pause the coroutine and return
control to the event loop. The event loop will then continue to run other
coroutines until the first await
statement has completed its task. The
coroutine will then resume from where it left off until it reaches the
second await
statement, and so on.
Coroutines are a powerful tool for writing asynchronous code in Python, allowing for efficient and scalable programs that can handle multiple tasks simultaneously.
14.4.1. Syntax
>>> async def hello():
... return 'hello'
>>>
>>>
>>> type(hello)
<class 'function'>
>>>
>>> type(hello())
<class 'coroutine'>
14.4.2. SetUp
>>> import asyncio
14.4.3. Coroutine Function
Coroutine function is the definition (
async def
)Also known as async functions
Defined by putting
async
in front of thedef
Only a coroutine function (
async def
) can useawait
Non-coroutine functions (
def
) cannot useawait
Coroutine functions are not awaitables
Calling a coroutine function does not execute it, but rather returns a coroutine object. This is analogous to generator functions - calling them doesn't execute the function, it returns a generator object, which we then use later.
>>> async def hello():
... return 'hello'
14.4.4. Coroutine Object
Coroutine function will create coroutine when called
Coroutine objects are awaitables
To execute coroutine object you can
await
itTo execute coroutine object you can
asyncio.run()
To schedule coroutine object:
ensure_future()
orcreate_task()
To execute a coroutine object, either:
use it in an expression with
await
in front of it, oruse
asyncio.run()
, orschedule it with
ensure_future()
orcreate_task()
.
>>> async def hello():
... return 'hello'
>>>
>>>
>>> asyncio.run(hello())
'hello'
14.4.5. Run Sequentially
All lines inside of coroutine function will be executed sequentially
>>> async def hello():
... await asyncio.sleep(0.1)
... return 'hello'
>>>
>>>
>>> asyncio.run(hello())
'hello'
All lines inside of coroutine function will be executed sequentially. When
await
happen, other coroutine will start running. When other coroutine
finishes, it returns to our function. Then next line is executed (which
could also be an await
statement:
>>> async def hello():
... await asyncio.sleep(0.1)
... await asyncio.sleep(0.1)
... await asyncio.sleep(0.1)
... return 'hello'
>>>
>>>
>>> asyncio.run(hello())
'hello'
14.4.6. Run Concurrently
To run coroutine objects use
asyncio.gather()
This won't execute in parallel (won't use multiple threads)
It will run concurrently (in a single thread)
>>> async def hello():
... await asyncio.sleep(0.1)
>>>
>>> async def main():
... await asyncio.gather(
... hello(),
... hello(),
... hello(),
... )
>>>
>>> asyncio.run(main())
14.4.7. Error: Created but not awaited
Created but not awaited objects will raise an exception
This prevents from creating coroutines and forgetting to "await" on it
14.4.8. Error: Running Coroutine Functions
Only coroutine objects can be run
It is not possible to run coroutine function
>>> def hello():
... return 'hello'
>>>
>>>
>>> asyncio.run(hello)
Traceback (most recent call last):
ValueError: a coroutine was expected, got <function hello at 0x...>
14.4.9. Error: Multiple Awaiting
Coroutine object can only be awaited once
>>> async def hello():
... return 'hello'
>>>
>>>
>>> coro = hello()
>>>
>>> asyncio.run(coro)
'hello'
>>>
>>> asyncio.run(coro)
Traceback (most recent call last):
RuntimeError: cannot reuse already awaited coroutine
14.4.10. Error: Await Outside Coroutine Function
Only a coroutine function (
async def
) can useawait
Non-coroutine functions (
def
) cannot useawait
>>> def hello():
... await asyncio.sleep(0.1)
... return 'hello'
...
Traceback (most recent call last):
SyntaxError: 'await' outside async function
14.4.11. Getting Results
>>> async def hello():
... await asyncio.sleep(0.1)
... return 'hello'
>>>
>>>
>>> async def main():
... return await hello()
>>>
>>>
>>> asyncio.run(main())
'hello'
>>> async def hello():
... await asyncio.sleep(0.1)
... return 'hello'
>>>
>>> async def main():
... return await asyncio.gather(
... hello(),
... hello(),
... hello(),
... )
>>>
>>> asyncio.run(main())
['hello', 'hello', 'hello']
14.4.12. Inspect
>>> from inspect import isawaitable
>>>
>>>
>>> async def hello():
... return 'hello'
>>>
>>>
>>> isawaitable(hello)
False
>>>
>>> isawaitable(hello())
True
>>>
>>>
>>> type(hello)
<class 'function'>
>>>
>>> type(hello())
<class 'coroutine'>
14.4.13. Case Study
import requests
urls = [
'https://python3.info/index.html',
'https://python3.info/LICENSE.html',
'https://python3.info/about/versions.html',
'https://python3.info/about/references.html',
'https://python3.info/about/history.html',
'https://python3.info/about/links.html',
]
def fetch(url):
print(f'fetch before: {url}')
response = requests.get(url)
print(f'fetch after: {url}')
return response.text
def main():
for url in urls:
print(f'main before {url}')
content = fetch(url)
print(f'main after {url}')
main()
# httpx
# aiohttp
import httpx
urls = [
'https://python3.info/index.html',
'https://python3.info/LICENSE.html',
'https://python3.info/about/versions.html',
'https://python3.info/about/references.html',
'https://python3.info/about/history.html',
'https://python3.info/about/links.html',
]
def fetch(url):
print(f'fetch before: {url}')
response = httpx.get(url)
print(f'fetch after: {url}')
return response.text
def main():
for url in urls:
print(f'main before {url}')
content = fetch(url)
print(f'main after {url}')
main()
# httpx
# aiohttp
import asyncio
import httpx
urls = [
'https://python3.info/index.html',
'https://python3.info/LICENSE.html',
'https://python3.info/about/versions.html',
'https://python3.info/about/references.html',
'https://python3.info/about/history.html',
'https://python3.info/about/links.html',
]
async def fetch(url):
print(f'fetch before: {url}')
response = httpx.get(url)
print(f'fetch after: {url}')
return response.text
async def main():
for url in urls:
print(f'main before {url}')
content = await fetch(url)
print(f'main after {url}')
asyncio.run(main())
# httpx
# aiohttp
import asyncio
import httpx
urls = [
'https://python3.info/index.html',
'https://python3.info/LICENSE.html',
'https://python3.info/about/versions.html',
'https://python3.info/about/references.html',
'https://python3.info/about/history.html',
'https://python3.info/about/links.html',
]
async def fetch(url):
print(f'fetch before: {url}')
ac = httpx.AsyncClient()
response = await ac.get(url)
print(f'fetch after: {url}')
return response.text
async def main():
todo = []
for url in urls:
todo.append(fetch(url))
result = await asyncio.gather(*todo)
asyncio.run(main())
# httpx
# aiohttp
import asyncio
import httpx
urls = [
'https://python3.info/index.html',
'https://python3.info/LICENSE.html',
'https://python3.info/about/versions.html',
'https://python3.info/about/references.html',
'https://python3.info/about/history.html',
'https://python3.info/about/links.html',
]
async def fetch(url):
print(f'fetch before: {url}')
async with httpx.AsyncClient() as client:
response = await client.get(url)
print(f'fetch after: {url}')
return response.text
async def main():
async with asyncio.TaskGroup() as group:
for url in urls:
group.create_task(fetch(url))
asyncio.run(main())