Skip to content

Instantly share code, notes, and snippets.

@darrenclark
Created November 19, 2025 15:38
Show Gist options
  • Select an option

  • Save darrenclark/cb1a9795cce7e66970c9ca32af12b879 to your computer and use it in GitHub Desktop.

Select an option

Save darrenclark/cb1a9795cce7e66970c9ca32af12b879 to your computer and use it in GitHub Desktop.
Python asyncio learnings
'''
Demonstrates that async functions do not run until awaited, unless
they are scheduled otherwise - using asyncio.create_task(...) or similar
Output
------
$ python 1_async_sequencing.py
===== without asyncio.create_task ======
calling async_function(1)
calling async_function(2)
awaiting async_fun1
async_function(1): start
async_function(1): doing work.
async_function(1): doing work..
async_function(1): doing work...
async_function(1): done
awaiting async_fun2
async_function(2): start
async_function(2): doing work.
async_function(2): doing work..
async_function(2): doing work...
async_function(2): done
===== using asyncio.create_task ======
calling asyncio.create_task(async_function(1))
calling asyncio.create_task(async_function(2))
awaiting async_task1
async_function(1): start
async_function(1): doing work.
async_function(2): start
async_function(2): doing work.
async_function(1): doing work..
async_function(2): doing work..
async_function(1): doing work...
async_function(2): doing work...
async_function(1): done
async_function(2): done
awaiting async_task2
'''
import asyncio
async def async_function(i):
print('async_function(%d): start' % i)
print('async_function(%d): doing work.' % i)
await asyncio.sleep(0.5)
print('async_function(%d): doing work..' % i)
await asyncio.sleep(0.5)
print('async_function(%d): doing work...' % i)
await asyncio.sleep(0.5)
print('async_function(%d): done' % i)
async def main():
print("\n===== without asyncio.create_task ======")
print("calling async_function(1)")
async_fun1 = async_function(1)
print("calling async_function(2)")
async_fun2 = async_function(2)
print("awaiting async_fun1")
await async_fun1
print("awaiting async_fun2")
await async_fun2
print("\n===== using asyncio.create_task ======")
print("calling asyncio.create_task(async_function(1))")
async_task1 = asyncio.create_task(async_function(1))
print("calling asyncio.create_task(async_function(2))")
async_task2 = asyncio.create_task(async_function(2))
print("awaiting async_task1")
await async_task1
print("awaiting async_task2")
await async_task2
if __name__ == '__main__':
asyncio.run(main())
'''
Demonstrates that async generators do not run AT ALL until iterated
over.
Output
------
$ python 2_async_generator.py
calling async_generator()
after calling async_generator()
async_generator: start
async_generator: yield 1
got value from async_generator(): 1
async_generator: yield 2
got value from async_generator(): 2
async_generator: done
'''
import asyncio
async def async_generator():
print('async_generator: start')
print('async_generator: yield 1')
yield 1
await asyncio.sleep(0.5)
print('async_generator: yield 2')
yield 2
print('async_generator: done')
async def main():
print("\ncalling async_generator()")
async_gen = async_generator()
print("after calling async_generator()")
async for value in async_gen:
print("got value from async_generator(): %d" % value)
if __name__ == '__main__':
asyncio.run(main())
'''
Demonstrates how cancellation works.
When cancelling a task, the CancelledError is propagated down to the inner
most await statement, then bubbles back up.
Output
------
$ python 3_async_cancellation.py
creating task & awaiting until inner_2 is reached
cancelling outer task (notice inner_2 gets the CancelledError first)
inner_2: CancelledError
inner_1: CancelledError
task: CancelledError
cancellation bubbled to main
'''
import asyncio
reached_inner_2 = asyncio.Event()
async def task():
try:
await inner_1()
except asyncio.CancelledError as e:
print("task: CancelledError")
raise e
async def inner_1():
try:
await inner_2()
except asyncio.CancelledError as e:
print("inner_1: CancelledError")
raise e
async def inner_2():
try:
reached_inner_2.set()
await asyncio.sleep(1)
except asyncio.CancelledError as e:
print("inner_2: CancelledError")
raise e
async def main():
print("\ncreating task & awaiting until inner_2 is reached")
t = asyncio.create_task(task())
await reached_inner_2.wait()
print("cancelling outer task (notice inner_2 gets the CancelledError first)")
t.cancel()
try:
await t
except asyncio.CancelledError:
print("cancellation bubbled to main")
if __name__ == '__main__':
asyncio.run(main())
'''
Demonstrates how timeouts works.
- the inner task gets cancelled (CancelledError)
- the outer code gets a TimeoutError
Output
------
$ python 4_async_timeout.py
==== asyncio.wait_for with timeout ====
asyncio.wait_for(...)
task: start
task: CancelledError
main: TimeoutError
==== async asyncio.timeout(..) ====
async with asyncio.timeout(...): ...
task: start
task: CancelledError
main: TimeoutError
'''
import asyncio
async def task():
print("task: start")
try:
await asyncio.sleep(5)
except asyncio.CancelledError as e:
print("task: CancelledError")
raise e
async def main():
print("\n==== asyncio.wait_for with timeout ====")
try:
t = asyncio.create_task(task())
print("asyncio.wait_for(...)")
await asyncio.wait_for(t, timeout=0.5)
except asyncio.TimeoutError:
print("main: TimeoutError")
print("\n==== async asyncio.timeout(..) ====")
try:
print("async with asyncio.timeout(...): ...")
async with asyncio.timeout(0.5):
await task()
except asyncio.TimeoutError:
print("main: TimeoutError")
if __name__ == '__main__':
asyncio.run(main())
'''
Demonstrates async futures.
- any awaitable can be converted to a future using asyncio.ensure_future(...)
- futures:
- can be awaited
- can be cancelled
- have callbacks attached that run when they complete
- can be queried for status (done, cancelled, result, exception)
Output
------
$ python 5_async_futures.py
==== asyncio.ensure_future(...) creates futures ====
create 'async_func_fut' from async function
create 'task_fut' from task
awaiting futures
job(async func): start
job(task): start
job(async func): returning
job(task): returning
==== future can be inspected ====
async_func_fut: done()=True, cancelled()=False, result()=async func
task_fut: done()=True, cancelled()=False, result()=task
==== futures can have callbacks ====
adding callback to callback_fut
awaiting callback_fut
job(callback): start
job(callback): returning
[callback] callback_fut done: result=callback
==== futures can be cancelled ====
job(cancel): start
cancelling cancel_fut
awaiting cancel_fut
task(cancel): CancelledError
main: CancelledError from cancel_fut
cancel_fut: done()=True, cancelled()=True
'''
import asyncio
async def job(n):
print("job(%s): start" % n)
try:
await asyncio.sleep(0.5)
print("job(%s): returning" % n)
return n
except asyncio.CancelledError as e:
print("task(%s): CancelledError" % n)
raise e
async def main():
print("\n==== asyncio.ensure_future(...) creates futures ====")
print("create 'async_func_fut' from async function")
async_func_fut = asyncio.ensure_future(job("async func"))
print("create 'task_fut' from task")
task_fut = asyncio.ensure_future(asyncio.create_task(job("task")))
print("awaiting futures")
await async_func_fut
await task_fut
print("\n==== future can be inspected ====")
print("async_func_fut: done()=%s, cancelled()=%s, result()=%s" % (
async_func_fut.done(),
async_func_fut.cancelled(),
async_func_fut.result()
))
print("task_fut: done()=%s, cancelled()=%s, result()=%s" % (
task_fut.done(),
task_fut.cancelled(),
task_fut.result()
))
print("\n==== futures can have callbacks ====")
callback_fut = asyncio.ensure_future(job("callback"))
print("adding callback to callback_fut")
callback_fut.add_done_callback(
lambda fut: print("[callback] callback_fut done: result=%s" % fut.result())
)
print("awaiting callback_fut")
await callback_fut
print("\n==== futures can be cancelled ====")
cancel_fut = asyncio.ensure_future(asyncio.create_task(job("cancel")))
await asyncio.sleep(0.0001)
print("cancelling cancel_fut")
cancel_fut.cancel()
print("awaiting cancel_fut")
try:
await cancel_fut
except asyncio.CancelledError:
print("main: CancelledError from cancel_fut")
print("cancel_fut: done()=%s, cancelled()=%s" % (
cancel_fut.done(),
cancel_fut.cancelled()
))
if __name__ == '__main__':
asyncio.run(main())
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment