Created
November 19, 2025 15:38
-
-
Save darrenclark/cb1a9795cce7e66970c9ca32af12b879 to your computer and use it in GitHub Desktop.
Python asyncio learnings
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| ''' | |
| 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()) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| ''' | |
| 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()) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| ''' | |
| 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()) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| ''' | |
| 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()) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| ''' | |
| 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