15.1. AsyncIO Run
asyncio.run()
is a main entrypointasyncio.gather()
can run concurrently and gather result (in order of its arguments)
In Python, asyncio is a module that provides tools for writing asynchronous
code using coroutines. The asyncio.run()
function is a utility function
that is used to run the main entry point of an asyncio program.
The asyncio.run()
function was introduced in Python 3.7 as a simple way
to run an asyncio program. It creates an event loop, runs the coroutine passed
to it, and then closes the event loop.
Here is an example of how to use asyncio.run()
:
>>> import asyncio
>>>
>>> async def my_coroutine():
... await asyncio.sleep(5)
... print("Task complete!")
>>>
>>> async def main():
... await my_coroutine()
>>>
>>> asyncio.run(main())
Task complete!
In this example, the my_coroutine
function is a coroutine that sleeps for
5 seconds and then prints "Task complete!". The main
function calls
my_coroutine
using the await
keyword. Finally, the asyncio.run()
function is used to run the main
coroutine.
When asyncio.run()
is called, it creates an event loop, runs the main
coroutine, and then closes the event loop. In this case, the my_coroutine
function will be executed, causing the program to sleep for 5 seconds before
printing "Task complete!".
The asyncio.run()
function is a convenient way to run an asyncio program
without having to manually create and manage an event loop. It simplifies
the process of writing asynchronous code in Python.
15.1.1. SetUp
>>> import asyncio
15.1.2. Run Coroutine
asyncio.run(coro, *, debug=False)
Execute the coroutine
coro
and return the resultTakes care of managing the asyncio event loop, finalizing asynchronous generators, and closing the threadpool
Cannot be called when another asyncio event loop is running in the same thread
Always creates a new event loop and closes it at the end
It should be used as a main entry point for asyncio programs, and should ideally only be called once
>>> async def hello():
... print('hello')
>>>
>>>
>>> asyncio.run(hello())
hello
15.1.3. Run Sequentially
>>> async def hello():
... print('hello')
>>>
>>>
>>> async def main():
... await hello()
... await hello()
... await hello()
>>>
>>>
>>> asyncio.run(main())
hello
hello
hello
15.1.4. Run Concurrently
awaitable
asyncio.gather(*aws, return_exceptions=False)
Run awaitable objects in the
aws
sequence concurrentlyIf any awaitable in
aws
is a coroutine, it is automatically scheduled as aTask
If all awaitables are completed successfully, the result is an aggregate list of returned values
The order of result values corresponds to the order of awaitables in
aws
return_exceptions=False
(default): the first raised exception is immediately propagated to the task that awaits ongather()
; other awaitables in theaws
sequence won't be cancelled and will continue to runreturn_exceptions=True
: exceptions are treated the same as successful results, and aggregated in the result listIf
gather()
is cancelled (ie. on timeout), all submitted awaitables (that have not completed yet) are also cancelledIf any
Task
orFuture
from theaws
sequence is cancelled, it is treated as if it raisedCancelledError
– thegather()
call is not cancelled in this caseThis is to prevent the cancellation of one submitted Task/Future to cause other Tasks/Futures to be cancelled
>>> async def a():
... print('a: started')
... await asyncio.sleep(0.2)
... print('a: finished')
... return 'a'
>>>
>>> async def b():
... print('b: started')
... await asyncio.sleep(0.1)
... print('b: finished')
... return 'b'
>>>
>>> async def c():
... print('c: started')
... await asyncio.sleep(0.3)
... print('c: finished')
... return 'c'
>>>
>>>
>>> async def main():
... result = await asyncio.gather(a(), b(), c())
... print(f'Result: {result}')
>>>
>>>
>>> asyncio.run(main())
a: started
b: started
c: started
b: finished
a: finished
c: finished
Result: ['a', 'b', 'c']
15.1.5. Run as Completed
asyncio.as_completed(aws, *, timeout=None)
Run awaitable objects in the
aws
iterable concurrentlyReturn an iterator of coroutines
Each coroutine returned can be awaited to get the earliest next result from the iterable of the remaining awaitables
Raises
asyncio.TimeoutError
if the timeout occurs before all Futures are done
>>> async def a():
... print('a: started')
... await asyncio.sleep(0.2)
... print('a: finished')
... return 'a'
>>>
>>> async def b():
... print('b: started')
... await asyncio.sleep(0.1)
... print('b: finished')
... return 'b'
>>>
>>> async def c():
... print('c: started')
... await asyncio.sleep(0.3)
... print('c: finished')
... return 'c'
>>>
>>>
>>> async def main():
... todo = [a(), b(), c()]
... for coro in asyncio.as_completed(todo):
... result = await coro
... print(result)
>>>
>>>
>>> asyncio.run(main())
a: started
c: started
b: started
b: finished
b
a: finished
a
c: finished
c
15.1.6. Run in Threads
coroutine
asyncio.to_thread(func, /, *args, **kwargs)
Asynchronously run function func in a separate thread.
Any
*args
and**kwargs
supplied for this function are directly passed to func.Return a coroutine that can be awaited to get the eventual result of func.
This coroutine function is intended to be used for executing IO-bound functions/methods that would otherwise block the event loop if they were ran in the main thread.
>>> import asyncio
>>> import time
>>>
>>>
>>> def work():
... print(f'Work started {time.strftime("%X")}')
... time.sleep(2) # Blocking
... print(f'Work done at {time.strftime("%X")}')
>>>
>>>
>>> async def main():
... print(f'Started main at {time.strftime("%X")}')
... await asyncio.gather(
... asyncio.to_thread(work),
... asyncio.sleep(1))
... print(f'Finished main at {time.strftime("%X")}')
>>>
>>>
>>> asyncio.run(main())
Started main at 22:53:40
Work started 22:53:40
Work done at 22:53:42
Finished main at 22:53:42
Due to the GIL, asyncio.to_thread()
can typically only be used to make
IO-bound functions non-blocking. However, for extension modules that
release the GIL or alternative Python implementations that don't have one,
asyncio.to_thread()
can also be used for CPU-bound functions.
15.1.7. Case Study
import asyncio
async def main():
print('main')
asyncio.run(main())
import asyncio
async def a():
print('a')
async def b():
print('b')
async def c():
print('c')
async def main():
await a()
await b()
await c()
asyncio.run(main())
import asyncio
async def a():
print('a')
async def b():
print('b')
async def c():
print('c')
async def main():
await asyncio.gather(a(), b(), c())
asyncio.run(main())
import asyncio
async def a():
print('a')
async def b():
print('b')
async def c():
print('c')
async def main():
result = await asyncio.gather(a(), b(), c())
print(result)
asyncio.run(main())
import asyncio
async def a():
print('a')
return 'a'
async def b():
print('b')
return 'b'
async def c():
print('c')
return 'c'
async def main():
result = await asyncio.gather(a(), b(), c())
print(result)
asyncio.run(main())
import asyncio
async def a():
print('a')
print('a')
print('a')
print('a')
print('a')
print('a')
return 'a'
async def b():
print('b')
print('b')
print('b')
print('b')
print('b')
print('b')
return 'b'
async def c():
print('c')
print('c')
print('c')
print('c')
print('c')
print('c')
return 'c'
async def main():
result = await asyncio.gather(a(), b(), c())
print(result)
asyncio.run(main())
import asyncio
import time
async def a():
print('a')
print('a')
print('a')
time.sleep(3)
print('a')
print('a')
print('a')
return 'a'
async def b():
print('b')
print('b')
print('b')
print('b')
print('b')
print('b')
return 'b'
async def c():
print('c')
print('c')
print('c')
print('c')
print('c')
print('c')
return 'c'
async def main():
result = await asyncio.gather(a(), b(), c())
print(result)
asyncio.run(main())
import asyncio
async def a():
print('a')
print('a')
print('a')
await asyncio.sleep(3) # for example database query, which takes long time
print('a')
print('a')
print('a')
return 'a'
async def b():
print('b')
print('b')
print('b')
print('b')
print('b')
print('b')
return 'b'
async def c():
print('c')
print('c')
print('c')
print('c')
print('c')
print('c')
return 'c'
async def main():
result = await asyncio.gather(a(), b(), c())
print(result)
asyncio.run(main())
import asyncio
import time
async def a():
print('a')
print('a')
await asyncio.sleep(3)
print('a')
print('a')
await asyncio.sleep(1)
print('a')
print('a')
return 'a'
async def b():
print('b')
print('b')
await asyncio.sleep(0.1)
print('b')
print('b')
await asyncio.sleep(2)
print('b')
print('b')
return 'b'
async def c():
print('c')
print('c')
await asyncio.sleep(1)
print('c')
print('c')
await asyncio.sleep(1.5)
print('c')
print('c')
return 'c'
async def main():
result = await asyncio.gather(a(), b(), c())
print(result)
asyncio.run(main())
15.1.8. Assignments
# %% License
# - Copyright 2025, Matt Harasymczuk <matt@python3.info>
# - This code can be used only for learning by humans
# - This code cannot be used for teaching others
# - This code cannot be used for teaching LLMs and AI algorithms
# - This code cannot be used in commercial or proprietary products
# - This code cannot be distributed in any form
# - This code cannot be changed in any form outside of training course
# - This code cannot have its license changed
# - If you use this code in your product, you must open-source it under GPLv2
# - Exception can be granted only by the author
# %% Run
# - PyCharm: right-click in the editor and `Run Doctest in ...`
# - PyCharm: keyboard shortcut `Control + Shift + F10`
# - Terminal: `python -m doctest -v myfile.py`
# %% About
# - Name: OOP Async GatherMany
# - Difficulty: easy
# - Lines: 2
# - Minutes: 3
# %% English
# 1. Define:
# - coroutine function `main()`
# 2. After running coroutine should:
# - execute coroutines a(), b() and c()
# - gather their returned values
# - return results
# 3. Run doctests - all must succeed
# %% Polish
# 1. Zdefiniuj:
# - coroutine function `main()`
# 2. Po uruchomieniu coroutine powinna:
# - wykonać korutyny a(), b() i c()
# - zebrać ich zwracane wartości
# - zwrócić wyniki
# 3. Uruchom doctesty - wszystkie muszą się powieść
# %% Tests
"""
>>> import sys; sys.tracebacklimit = 0
>>> assert sys.version_info >= (3, 10), \
'Python 3.10+ required'
>>> from inspect import iscoroutine, iscoroutinefunction
>>> import asyncio
>>> assert iscoroutinefunction(a)
>>> assert iscoroutinefunction(b)
>>> assert iscoroutinefunction(c)
>>> assert iscoroutine(a())
>>> assert iscoroutine(b())
>>> assert iscoroutine(c())
>>> assert iscoroutinefunction(main)
>>> assert iscoroutine(main())
>>> asyncio.run(main())
a: before
b: before
c: before
b: after
a: after
c: after
['a', 'b', 'c']
"""
import asyncio
async def a():
print('a: before')
await asyncio.sleep(1.0)
print('a: after')
return 'a'
async def b():
print('b: before')
await asyncio.sleep(0.5)
print('b: after')
return 'b'
async def c():
print('c: before')
await asyncio.sleep(1.5)
print('c: after')
return 'c'
# coroutine function `main()`
# execute coroutines a(), b() and c(); return gathered results
# type: Coroutine
def main():
...
# %% License
# - Copyright 2025, Matt Harasymczuk <matt@python3.info>
# - This code can be used only for learning by humans
# - This code cannot be used for teaching others
# - This code cannot be used for teaching LLMs and AI algorithms
# - This code cannot be used in commercial or proprietary products
# - This code cannot be distributed in any form
# - This code cannot be changed in any form outside of training course
# - This code cannot have its license changed
# - If you use this code in your product, you must open-source it under GPLv2
# - Exception can be granted only by the author
# %% Run
# - PyCharm: right-click in the editor and `Run Doctest in ...`
# - PyCharm: keyboard shortcut `Control + Shift + F10`
# - Terminal: `python -m doctest -v myfile.py`
# %% About
# - Name: OOP Async GatherParams
# - Difficulty: easy
# - Lines: 9
# - Minutes: 5
# %% English
# 1. Define:
# - coroutine function `run()`
# - coroutine function `main()`
# 2. Coroutine `main()` should schedule `run()` 3 times with parameters:
# - First: name=a, sleep=1.0
# - Second: name=b, sleep=0.5
# - Third: name=c, sleep=1.5
# 3. Coroutine `main()` should return gathered results
# 4. Run doctests - all must succeed
# %% Polish
# 1. Zdefiniuj:
# - coroutine function `run()`
# - coroutine function `main()`
# 2. Korutyna `main()` powinna zaschedulować `run()` 3 razy z parametrami:
# - Pierwsze: name=a, sleep=1.0
# - Drugie: name=b, sleep=0.5
# - Trzecie: name=c, sleep=1.5
# 3. Uruchom doctesty - wszystkie muszą się powieść
# %% Tests
"""
>>> import sys; sys.tracebacklimit = 0
>>> assert sys.version_info >= (3, 10), \
'Python 3.10+ required'
>>> from inspect import iscoroutine, iscoroutinefunction
>>> import asyncio
>>> assert iscoroutinefunction(run)
>>> assert iscoroutine(run(None,0))
>>> assert iscoroutinefunction(main)
>>> assert iscoroutine(main())
>>> asyncio.run(main())
['a', 'b', 'c']
"""
import asyncio
# type: Coroutine
def run():
...
# coroutine function `main()`
# type: Coroutine
def main():
...
# %% License
# - Copyright 2025, Matt Harasymczuk <matt@python3.info>
# - This code can be used only for learning by humans
# - This code cannot be used for teaching others
# - This code cannot be used for teaching LLMs and AI algorithms
# - This code cannot be used in commercial or proprietary products
# - This code cannot be distributed in any form
# - This code cannot be changed in any form outside of training course
# - This code cannot have its license changed
# - If you use this code in your product, you must open-source it under GPLv2
# - Exception can be granted only by the author
# %% Run
# - PyCharm: right-click in the editor and `Run Doctest in ...`
# - PyCharm: keyboard shortcut `Control + Shift + F10`
# - Terminal: `python -m doctest -v myfile.py`
# %% About
# - Name: OOP Async Fetch
# - Difficulty: easy
# - Lines: 7
# - Minutes: 8
# %% English
# 1. Define:
# - coroutine function `check()`
# - coroutine function `main()`
# 2. Coroutine `check()` should use coroutine `fetch()` to download html
# 3. Coroutine `check()` should check if string 'Matt Harasymczuk' is in html
# 4. Coroutine `main()` should schedule `check()` for each URL in DATA
# 5. Coroutine `main()` should return gathered results as list[dict], for example:
# [{'url': 'https://python3.info', 'license': True},
# {'url': 'https://python3.info/index.html', 'license': True},
# {'url': 'https://python3.info/about.html', 'license': False},
# {'url': 'https://python3.info/LICENSE.html', 'license': True}]
# 6. Run doctests - all must succeed
# %% Polish
# 1. Zdefiniuj:
# - coroutine function `check()`
# - coroutine function `main()`
# 2. Korutyna `check` powinna użyć korutyny `fetch()` aby ściągnąć html
# 3. Korutyna `check()` powinna sprawdzać czy string 'Matt Harasymczuk' jest w htmlu
# 4. Korutyna `main()` powinna zaschedulować `check()` dla każdego URL w DATA
# 5. Korutyna `main()` powinna zwrócić zebrane wyniki jako list[dict], na przykład:
# [{'url': 'https://python3.info', 'license': True},
# {'url': 'https://python3.info/index.html', 'license': True},
# {'url': 'https://python3.info/about.html', 'license': False},
# {'url': 'https://python3.info/LICENSE.html', 'license': True}]
# 6. Uruchom doctesty - wszystkie muszą się powieść
# %% Tests
"""
>>> import sys; sys.tracebacklimit = 0
>>> assert sys.version_info >= (3, 10), \
'Python 3.10+ required'
>>> from inspect import iscoroutine, iscoroutinefunction
>>> import asyncio
>>> assert iscoroutinefunction(fetch)
>>> assert iscoroutine(fetch(''))
>>> assert iscoroutinefunction(check)
>>> assert iscoroutine(check(''))
>>> assert iscoroutinefunction(main)
>>> assert iscoroutine(main())
>>> asyncio.run(main()) # doctest: +NORMALIZE_WHITESPACE
[{'url': 'https://python3.info', 'license': True},
{'url': 'https://python3.info/index.html', 'license': True},
{'url': 'https://python3.info/about.html', 'license': False},
{'url': 'https://python3.info/LICENSE.html', 'license': True}]
"""
import asyncio
from httpx import AsyncClient
DATA = [
'https://python3.info',
'https://python3.info/index.html',
'https://python3.info/about.html',
'https://python3.info/LICENSE.html',
]
async def fetch(url):
return await AsyncClient().get(url)