[{"data":1,"prerenderedAt":924},["ShallowReactive",2],{"post-en-asyncio-production":3},{"id":4,"title":5,"body":6,"date":909,"description":910,"excerpt":911,"extension":912,"meta":913,"navigation":61,"path":914,"readTime":100,"seo":915,"slug":916,"stem":917,"tags":918,"__hash__":923},"en_blog/en/blog/asyncio-production.md","Asyncio in Production: The Pitfalls Tutorials Never Cover",{"type":7,"value":8,"toc":898},"minimark",[9,14,23,32,39,150,159,166,225,229,239,307,317,321,328,358,365,368,414,420,424,427,525,535,539,542,622,641,645,648,769,776,780,783,841,844,872,875,879,894],[10,11,13],"h1",{"id":12},"asyncio-in-production-the-pitfalls-tutorials-never-cover","asyncio in Production: The Pitfalls Tutorials Never Cover",[15,16,17,18,22],"p",{},"asyncio tutorials almost invariably stop at the same point: ",[19,20,21],"code",{},"await asyncio.gather(task1(), task2())",", a handful of coroutine examples, and nothing further. Production is more demanding. Silent exceptions, tasks that never complete, shutdowns that hang — here are the real problems and how to address them.",[24,25,27,28,31],"h2",{"id":26},"the-problem-with-asynciogather-and-exceptions","The Problem with ",[19,29,30],{},"asyncio.gather"," and Exceptions",[15,33,34,35,38],{},"The default behaviour of ",[19,36,37],{},"gather"," is counterintuitive:",[40,41,46],"pre",{"className":42,"code":43,"language":44,"meta":45,"style":45},"language-python shiki shiki-themes github-dark github-light","import asyncio\n\nasync def task_ok():\n    await asyncio.sleep(1)\n    return \"ok\"\n\nasync def task_fail():\n    await asyncio.sleep(0.5)\n    raise ValueError(\"something went wrong\")\n\nasync def main():\n    results = await asyncio.gather(task_ok(), task_fail())\n    print(results)  # Never reached\n\nasyncio.run(main())\n# ValueError: something went wrong\n# task_ok() was silently cancelled\n","python","",[19,47,48,56,63,69,75,81,86,92,98,104,109,115,121,127,132,138,144],{"__ignoreMap":45},[49,50,53],"span",{"class":51,"line":52},"line",1,[49,54,55],{},"import asyncio\n",[49,57,59],{"class":51,"line":58},2,[49,60,62],{"emptyLinePlaceholder":61},true,"\n",[49,64,66],{"class":51,"line":65},3,[49,67,68],{},"async def task_ok():\n",[49,70,72],{"class":51,"line":71},4,[49,73,74],{},"    await asyncio.sleep(1)\n",[49,76,78],{"class":51,"line":77},5,[49,79,80],{},"    return \"ok\"\n",[49,82,84],{"class":51,"line":83},6,[49,85,62],{"emptyLinePlaceholder":61},[49,87,89],{"class":51,"line":88},7,[49,90,91],{},"async def task_fail():\n",[49,93,95],{"class":51,"line":94},8,[49,96,97],{},"    await asyncio.sleep(0.5)\n",[49,99,101],{"class":51,"line":100},9,[49,102,103],{},"    raise ValueError(\"something went wrong\")\n",[49,105,107],{"class":51,"line":106},10,[49,108,62],{"emptyLinePlaceholder":61},[49,110,112],{"class":51,"line":111},11,[49,113,114],{},"async def main():\n",[49,116,118],{"class":51,"line":117},12,[49,119,120],{},"    results = await asyncio.gather(task_ok(), task_fail())\n",[49,122,124],{"class":51,"line":123},13,[49,125,126],{},"    print(results)  # Never reached\n",[49,128,130],{"class":51,"line":129},14,[49,131,62],{"emptyLinePlaceholder":61},[49,133,135],{"class":51,"line":134},15,[49,136,137],{},"asyncio.run(main())\n",[49,139,141],{"class":51,"line":140},16,[49,142,143],{},"# ValueError: something went wrong\n",[49,145,147],{"class":51,"line":146},17,[49,148,149],{},"# task_ok() was silently cancelled\n",[15,151,152,154,155,158],{},[19,153,37],{}," raises the first exception and cancels the remaining tasks without any warning. If ",[19,156,157],{},"task_ok()"," was writing to a database, the result is partially committed.",[15,160,161,162,165],{},"The solution: ",[19,163,164],{},"return_exceptions=True"," collects all exceptions without interrupting sibling tasks:",[40,167,169],{"className":42,"code":168,"language":44,"meta":45,"style":45},"async def main():\n    results = await asyncio.gather(\n        task_ok(),\n        task_fail(),\n        return_exceptions=True\n    )\n    for result in results:\n        if isinstance(result, Exception):\n            logger.error(f\"Task failed: {result}\")\n        else:\n            logger.info(f\"Result: {result}\")\n",[19,170,171,175,180,185,190,195,200,205,210,215,220],{"__ignoreMap":45},[49,172,173],{"class":51,"line":52},[49,174,114],{},[49,176,177],{"class":51,"line":58},[49,178,179],{},"    results = await asyncio.gather(\n",[49,181,182],{"class":51,"line":65},[49,183,184],{},"        task_ok(),\n",[49,186,187],{"class":51,"line":71},[49,188,189],{},"        task_fail(),\n",[49,191,192],{"class":51,"line":77},[49,193,194],{},"        return_exceptions=True\n",[49,196,197],{"class":51,"line":83},[49,198,199],{},"    )\n",[49,201,202],{"class":51,"line":88},[49,203,204],{},"    for result in results:\n",[49,206,207],{"class":51,"line":94},[49,208,209],{},"        if isinstance(result, Exception):\n",[49,211,212],{"class":51,"line":100},[49,213,214],{},"            logger.error(f\"Task failed: {result}\")\n",[49,216,217],{"class":51,"line":106},[49,218,219],{},"        else:\n",[49,221,222],{"class":51,"line":111},[49,223,224],{},"            logger.info(f\"Result: {result}\")\n",[24,226,228],{"id":227},"taskgroup-the-better-api-since-python-311","TaskGroup: The Better API Since Python 3.11",[15,230,231,232,235,236,238],{},"Python 3.11 introduced ",[19,233,234],{},"asyncio.TaskGroup",", which corrects ",[19,237,37],{},"'s behaviour in a structured way:",[40,240,242],{"className":42,"code":241,"language":44,"meta":45,"style":45},"async def main():\n    results = []\n    try:\n        async with asyncio.TaskGroup() as tg:\n            t1 = tg.create_task(task_ok())\n            t2 = tg.create_task(task_fail())\n    except* ValueError as eg:\n        for exc in eg.exceptions:\n            logger.error(f\"Error: {exc}\")\n\n    # t1 and t2 are guaranteed to be complete here\n    if not t1.cancelled():\n        results.append(t1.result())\n",[19,243,244,248,253,258,263,268,273,278,283,288,292,297,302],{"__ignoreMap":45},[49,245,246],{"class":51,"line":52},[49,247,114],{},[49,249,250],{"class":51,"line":58},[49,251,252],{},"    results = []\n",[49,254,255],{"class":51,"line":65},[49,256,257],{},"    try:\n",[49,259,260],{"class":51,"line":71},[49,261,262],{},"        async with asyncio.TaskGroup() as tg:\n",[49,264,265],{"class":51,"line":77},[49,266,267],{},"            t1 = tg.create_task(task_ok())\n",[49,269,270],{"class":51,"line":83},[49,271,272],{},"            t2 = tg.create_task(task_fail())\n",[49,274,275],{"class":51,"line":88},[49,276,277],{},"    except* ValueError as eg:\n",[49,279,280],{"class":51,"line":94},[49,281,282],{},"        for exc in eg.exceptions:\n",[49,284,285],{"class":51,"line":100},[49,286,287],{},"            logger.error(f\"Error: {exc}\")\n",[49,289,290],{"class":51,"line":106},[49,291,62],{"emptyLinePlaceholder":61},[49,293,294],{"class":51,"line":111},[49,295,296],{},"    # t1 and t2 are guaranteed to be complete here\n",[49,298,299],{"class":51,"line":117},[49,300,301],{},"    if not t1.cancelled():\n",[49,303,304],{"class":51,"line":123},[49,305,306],{},"        results.append(t1.result())\n",[15,308,309,312,313,316],{},[19,310,311],{},"TaskGroup"," uses the ",[19,314,315],{},"except*"," syntax (ExceptionGroup) — all exceptions are collected, and the group waits for every task to finish or be cancelled before propagating. No more phantom tasks.",[24,318,320],{"id":319},"background-tasks-in-fastapi","Background Tasks in FastAPI",[15,322,323,324,327],{},"A common FastAPI pattern is launching background work with ",[19,325,326],{},"asyncio.create_task",". The classic pitfall:",[40,329,331],{"className":42,"code":330,"language":44,"meta":45,"style":45},"# WRONG — the task can be silently garbage-collected\n@app.post(\"/process\")\nasync def process(data: dict):\n    asyncio.create_task(long_running_task(data))  # Reference lost\n    return {\"status\": \"started\"}\n",[19,332,333,338,343,348,353],{"__ignoreMap":45},[49,334,335],{"class":51,"line":52},[49,336,337],{},"# WRONG — the task can be silently garbage-collected\n",[49,339,340],{"class":51,"line":58},[49,341,342],{},"@app.post(\"/process\")\n",[49,344,345],{"class":51,"line":65},[49,346,347],{},"async def process(data: dict):\n",[49,349,350],{"class":51,"line":71},[49,351,352],{},"    asyncio.create_task(long_running_task(data))  # Reference lost\n",[49,354,355],{"class":51,"line":77},[49,356,357],{},"    return {\"status\": \"started\"}\n",[15,359,360,361,364],{},"asyncio does not hold a strong reference to tasks created with ",[19,362,363],{},"create_task",". If the garbage collector runs at the right moment, the task is silently cancelled.",[15,366,367],{},"The correct approach:",[40,369,371],{"className":42,"code":370,"language":44,"meta":45,"style":45},"# In the application state\nbackground_tasks: set[asyncio.Task] = set()\n\n@app.post(\"/process\")\nasync def process(data: dict):\n    task = asyncio.create_task(long_running_task(data))\n    background_tasks.add(task)\n    task.add_done_callback(background_tasks.discard)  # Automatic cleanup\n    return {\"status\": \"started\"}\n",[19,372,373,378,383,387,391,395,400,405,410],{"__ignoreMap":45},[49,374,375],{"class":51,"line":52},[49,376,377],{},"# In the application state\n",[49,379,380],{"class":51,"line":58},[49,381,382],{},"background_tasks: set[asyncio.Task] = set()\n",[49,384,385],{"class":51,"line":65},[49,386,62],{"emptyLinePlaceholder":61},[49,388,389],{"class":51,"line":71},[49,390,342],{},[49,392,393],{"class":51,"line":77},[49,394,347],{},[49,396,397],{"class":51,"line":83},[49,398,399],{},"    task = asyncio.create_task(long_running_task(data))\n",[49,401,402],{"class":51,"line":88},[49,403,404],{},"    background_tasks.add(task)\n",[49,406,407],{"class":51,"line":94},[49,408,409],{},"    task.add_done_callback(background_tasks.discard)  # Automatic cleanup\n",[49,411,412],{"class":51,"line":100},[49,413,357],{},[15,415,416,419],{},[19,417,418],{},"add_done_callback"," removes the task from the set when it completes — no memory leak, no silent cancellation.",[24,421,423],{"id":422},"timeouts-on-network-operations","Timeouts on Network Operations",[15,425,426],{},"Without an explicit timeout, a network operation can suspend a coroutine indefinitely:",[40,428,430],{"className":42,"code":429,"language":44,"meta":45,"style":45},"import asyncio\nfrom httpx import AsyncClient\n\n# WRONG — can block forever\nasync def fetch_data(url: str) -> dict:\n    async with AsyncClient() as client:\n        response = await client.get(url)\n        return response.json()\n\n# CORRECT — explicit timeout\nasync def fetch_data(url: str, timeout: float = 10.0) -> dict:\n    try:\n        async with asyncio.timeout(timeout):  # Python 3.11+\n            async with AsyncClient() as client:\n                response = await client.get(url)\n                return response.json()\n    except asyncio.TimeoutError:\n        logger.error(f\"Timed out after {timeout}s on {url}\")\n        raise\n",[19,431,432,436,441,445,450,455,460,465,470,474,479,484,488,493,498,503,508,513,519],{"__ignoreMap":45},[49,433,434],{"class":51,"line":52},[49,435,55],{},[49,437,438],{"class":51,"line":58},[49,439,440],{},"from httpx import AsyncClient\n",[49,442,443],{"class":51,"line":65},[49,444,62],{"emptyLinePlaceholder":61},[49,446,447],{"class":51,"line":71},[49,448,449],{},"# WRONG — can block forever\n",[49,451,452],{"class":51,"line":77},[49,453,454],{},"async def fetch_data(url: str) -> dict:\n",[49,456,457],{"class":51,"line":83},[49,458,459],{},"    async with AsyncClient() as client:\n",[49,461,462],{"class":51,"line":88},[49,463,464],{},"        response = await client.get(url)\n",[49,466,467],{"class":51,"line":94},[49,468,469],{},"        return response.json()\n",[49,471,472],{"class":51,"line":100},[49,473,62],{"emptyLinePlaceholder":61},[49,475,476],{"class":51,"line":106},[49,477,478],{},"# CORRECT — explicit timeout\n",[49,480,481],{"class":51,"line":111},[49,482,483],{},"async def fetch_data(url: str, timeout: float = 10.0) -> dict:\n",[49,485,486],{"class":51,"line":117},[49,487,257],{},[49,489,490],{"class":51,"line":123},[49,491,492],{},"        async with asyncio.timeout(timeout):  # Python 3.11+\n",[49,494,495],{"class":51,"line":129},[49,496,497],{},"            async with AsyncClient() as client:\n",[49,499,500],{"class":51,"line":134},[49,501,502],{},"                response = await client.get(url)\n",[49,504,505],{"class":51,"line":140},[49,506,507],{},"                return response.json()\n",[49,509,510],{"class":51,"line":146},[49,511,512],{},"    except asyncio.TimeoutError:\n",[49,514,516],{"class":51,"line":515},18,[49,517,518],{},"        logger.error(f\"Timed out after {timeout}s on {url}\")\n",[49,520,522],{"class":51,"line":521},19,[49,523,524],{},"        raise\n",[15,526,527,530,531,534],{},[19,528,529],{},"asyncio.timeout()"," (Python 3.11+) is cleaner than ",[19,532,533],{},"asyncio.wait_for()"," for code blocks: it integrates naturally as a context manager and cleanly cancels all nested operations when the timeout fires.",[24,536,538],{"id":537},"clean-shutdown-in-fastapi","Clean Shutdown in FastAPI",[15,540,541],{},"An underappreciated problem: when FastAPI receives SIGTERM — a Kubernetes pod termination, a redeployment — in-progress tasks must complete cleanly.",[40,543,545],{"className":42,"code":544,"language":44,"meta":45,"style":45},"from contextlib import asynccontextmanager\nimport asyncio\n\nbackground_tasks: set[asyncio.Task] = set()\n\n@asynccontextmanager\nasync def lifespan(app: FastAPI):\n    # Startup\n    yield\n    # Shutdown — wait for in-progress tasks\n    if background_tasks:\n        logger.info(f\"Waiting for {len(background_tasks)} task(s) to complete...\")\n        await asyncio.gather(*background_tasks, return_exceptions=True)\n        logger.info(\"All tasks completed.\")\n\napp = FastAPI(lifespan=lifespan)\n",[19,546,547,552,556,560,564,568,573,578,583,588,593,598,603,608,613,617],{"__ignoreMap":45},[49,548,549],{"class":51,"line":52},[49,550,551],{},"from contextlib import asynccontextmanager\n",[49,553,554],{"class":51,"line":58},[49,555,55],{},[49,557,558],{"class":51,"line":65},[49,559,62],{"emptyLinePlaceholder":61},[49,561,562],{"class":51,"line":71},[49,563,382],{},[49,565,566],{"class":51,"line":77},[49,567,62],{"emptyLinePlaceholder":61},[49,569,570],{"class":51,"line":83},[49,571,572],{},"@asynccontextmanager\n",[49,574,575],{"class":51,"line":88},[49,576,577],{},"async def lifespan(app: FastAPI):\n",[49,579,580],{"class":51,"line":94},[49,581,582],{},"    # Startup\n",[49,584,585],{"class":51,"line":100},[49,586,587],{},"    yield\n",[49,589,590],{"class":51,"line":106},[49,591,592],{},"    # Shutdown — wait for in-progress tasks\n",[49,594,595],{"class":51,"line":111},[49,596,597],{},"    if background_tasks:\n",[49,599,600],{"class":51,"line":117},[49,601,602],{},"        logger.info(f\"Waiting for {len(background_tasks)} task(s) to complete...\")\n",[49,604,605],{"class":51,"line":123},[49,606,607],{},"        await asyncio.gather(*background_tasks, return_exceptions=True)\n",[49,609,610],{"class":51,"line":129},[49,611,612],{},"        logger.info(\"All tasks completed.\")\n",[49,614,615],{"class":51,"line":134},[49,616,62],{"emptyLinePlaceholder":61},[49,618,619],{"class":51,"line":140},[49,620,621],{},"app = FastAPI(lifespan=lifespan)\n",[15,623,624,625,628,629,632,633,636,637,640],{},"The ",[19,626,627],{},"lifespan"," context manager replaces the deprecated ",[19,630,631],{},"@app.on_event(\"startup\")"," and ",[19,634,635],{},"@app.on_event(\"shutdown\")"," hooks. The ",[19,638,639],{},"yield"," cleanly separates the startup and shutdown phases.",[24,642,644],{"id":643},"avoiding-event-loop-blockage","Avoiding Event Loop Blockage",[15,646,647],{},"asyncio is single-threaded. A synchronous blocking call inside a coroutine blocks the entire application:",[40,649,651],{"className":42,"code":650,"language":44,"meta":45,"style":45},"import asyncio\nfrom concurrent.futures import ThreadPoolExecutor\n\nexecutor = ThreadPoolExecutor(max_workers=4)\n\n# WRONG — blocks the event loop\nasync def process_file(path: str) -> str:\n    with open(path) as f:\n        return f.read()  # Synchronous I/O inside a coroutine\n\n# CORRECT — delegate to the thread pool\nasync def process_file(path: str) -> str:\n    loop = asyncio.get_event_loop()\n    return await loop.run_in_executor(\n        executor,\n        lambda: open(path).read()\n    )\n\n# Better still — aiofiles for file I/O\nimport aiofiles\n\nasync def process_file(path: str) -> str:\n    async with aiofiles.open(path) as f:\n        return await f.read()\n",[19,652,653,657,662,666,671,675,680,685,690,695,699,704,708,713,718,723,728,732,736,741,747,752,757,763],{"__ignoreMap":45},[49,654,655],{"class":51,"line":52},[49,656,55],{},[49,658,659],{"class":51,"line":58},[49,660,661],{},"from concurrent.futures import ThreadPoolExecutor\n",[49,663,664],{"class":51,"line":65},[49,665,62],{"emptyLinePlaceholder":61},[49,667,668],{"class":51,"line":71},[49,669,670],{},"executor = ThreadPoolExecutor(max_workers=4)\n",[49,672,673],{"class":51,"line":77},[49,674,62],{"emptyLinePlaceholder":61},[49,676,677],{"class":51,"line":83},[49,678,679],{},"# WRONG — blocks the event loop\n",[49,681,682],{"class":51,"line":88},[49,683,684],{},"async def process_file(path: str) -> str:\n",[49,686,687],{"class":51,"line":94},[49,688,689],{},"    with open(path) as f:\n",[49,691,692],{"class":51,"line":100},[49,693,694],{},"        return f.read()  # Synchronous I/O inside a coroutine\n",[49,696,697],{"class":51,"line":106},[49,698,62],{"emptyLinePlaceholder":61},[49,700,701],{"class":51,"line":111},[49,702,703],{},"# CORRECT — delegate to the thread pool\n",[49,705,706],{"class":51,"line":117},[49,707,684],{},[49,709,710],{"class":51,"line":123},[49,711,712],{},"    loop = asyncio.get_event_loop()\n",[49,714,715],{"class":51,"line":129},[49,716,717],{},"    return await loop.run_in_executor(\n",[49,719,720],{"class":51,"line":134},[49,721,722],{},"        executor,\n",[49,724,725],{"class":51,"line":140},[49,726,727],{},"        lambda: open(path).read()\n",[49,729,730],{"class":51,"line":146},[49,731,199],{},[49,733,734],{"class":51,"line":515},[49,735,62],{"emptyLinePlaceholder":61},[49,737,738],{"class":51,"line":521},[49,739,740],{},"# Better still — aiofiles for file I/O\n",[49,742,744],{"class":51,"line":743},20,[49,745,746],{},"import aiofiles\n",[49,748,750],{"class":51,"line":749},21,[49,751,62],{"emptyLinePlaceholder":61},[49,753,755],{"class":51,"line":754},22,[49,756,684],{},[49,758,760],{"class":51,"line":759},23,[49,761,762],{},"    async with aiofiles.open(path) as f:\n",[49,764,766],{"class":51,"line":765},24,[49,767,768],{},"        return await f.read()\n",[15,770,771,772,775],{},"The rule: any operation that takes more than a few milliseconds and is not natively async must be offloaded via ",[19,773,774],{},"run_in_executor"," or a dedicated async library.",[24,777,779],{"id":778},"debugging-coroutines-that-never-execute","Debugging Coroutines That Never Execute",[15,781,782],{},"A common beginner pitfall:",[40,784,786],{"className":42,"code":785,"language":44,"meta":45,"style":45},"async def my_coroutine():\n    print(\"executed\")\n\n# WRONG — creates a coroutine object without executing it\nmy_coroutine()\n# RuntimeWarning: coroutine 'my_coroutine' was never awaited\n\n# CORRECT\nawait my_coroutine()\n# or\nasyncio.run(my_coroutine())\n",[19,787,788,793,798,802,807,812,817,821,826,831,836],{"__ignoreMap":45},[49,789,790],{"class":51,"line":52},[49,791,792],{},"async def my_coroutine():\n",[49,794,795],{"class":51,"line":58},[49,796,797],{},"    print(\"executed\")\n",[49,799,800],{"class":51,"line":65},[49,801,62],{"emptyLinePlaceholder":61},[49,803,804],{"class":51,"line":71},[49,805,806],{},"# WRONG — creates a coroutine object without executing it\n",[49,808,809],{"class":51,"line":77},[49,810,811],{},"my_coroutine()\n",[49,813,814],{"class":51,"line":83},[49,815,816],{},"# RuntimeWarning: coroutine 'my_coroutine' was never awaited\n",[49,818,819],{"class":51,"line":88},[49,820,62],{"emptyLinePlaceholder":61},[49,822,823],{"class":51,"line":94},[49,824,825],{},"# CORRECT\n",[49,827,828],{"class":51,"line":100},[49,829,830],{},"await my_coroutine()\n",[49,832,833],{"class":51,"line":106},[49,834,835],{},"# or\n",[49,837,838],{"class":51,"line":111},[49,839,840],{},"asyncio.run(my_coroutine())\n",[15,842,843],{},"Enabling asyncio's debug mode surfaces this and other anomalies:",[40,845,847],{"className":42,"code":846,"language":44,"meta":45,"style":45},"import asyncio\nimport logging\n\nlogging.basicConfig(level=logging.DEBUG)\nasyncio.run(main(), debug=True)\n",[19,848,849,853,858,862,867],{"__ignoreMap":45},[49,850,851],{"class":51,"line":52},[49,852,55],{},[49,854,855],{"class":51,"line":58},[49,856,857],{},"import logging\n",[49,859,860],{"class":51,"line":65},[49,861,62],{"emptyLinePlaceholder":61},[49,863,864],{"class":51,"line":71},[49,865,866],{},"logging.basicConfig(level=logging.DEBUG)\n",[49,868,869],{"class":51,"line":77},[49,870,871],{},"asyncio.run(main(), debug=True)\n",[15,873,874],{},"In debug mode, asyncio logs unawaited coroutines, tasks that take longer than 100ms to execute (a sign of event loop blockage), and resources that are not properly closed.",[24,876,878],{"id":877},"key-takeaways","Key Takeaways",[15,880,881,882,884,885,887,888,890,891,893],{},"asyncio is powerful, but its default behaviours are occasionally surprising. The essentials: use ",[19,883,311],{}," over ",[19,886,37],{}," on Python 3.11+, always maintain a strong reference to tasks created with ",[19,889,363],{},", add explicit timeouts to all network operations, and handle shutdown cleanly in FastAPI's ",[19,892,627],{},". These four practices address the vast majority of production issues encountered with asyncio.",[895,896,897],"style",{},"html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}",{"title":45,"searchDepth":58,"depth":58,"links":899},[900,902,903,904,905,906,907,908],{"id":26,"depth":58,"text":901},"The Problem with asyncio.gather and Exceptions",{"id":227,"depth":58,"text":228},{"id":319,"depth":58,"text":320},{"id":422,"depth":58,"text":423},{"id":537,"depth":58,"text":538},{"id":643,"depth":58,"text":644},{"id":778,"depth":58,"text":779},{"id":877,"depth":58,"text":878},"2024-08-19","asyncio tutorials almost invariably stop at the same point: await asyncio.gather(task1(), task2()), a handful of coroutine examples, and nothing further. Production is more demanding. Silent exceptions, tasks that never complete, shutdowns that hang — here are the real problems and how to address them.",null,"md",{},"/en/blog/asyncio-production",{"title":5,"description":910},"asyncio-production","en/blog/asyncio-production",[919,920,921,922],"Python","asyncio","FastAPI","Concurrency","R3ROVoDwjFI9k3_IHXjxihwKuaZ-XR6vwovlGIcgX_0",1774645635818]