[{"data":1,"prerenderedAt":3930},["ShallowReactive",2],{"page-\u002Fpython\u002F22-async-await":3},{"id":4,"title":5,"body":6,"description":27,"extension":3924,"meta":3925,"navigation":51,"path":3926,"seo":3927,"stem":3928,"__hash__":3929},"content\u002Fpython\u002F22-async-await.md","22 — Async \u002F Await",{"type":7,"value":8,"toc":3901},"minimark",[9,13,18,888,1446,1450,1654,1681,1688,1943,1962,1972,2125,2145,2343,2347,2499,2520,2524,2529,2661,2685,2815,2819,2932,2940,3004,3009,3016,3131,3138,3275,3294,3438,3442,3503,3507,3576,3580,3583,3723,3839,3843,3897],[10,11,5],"h1",{"id":12},"_22-async-await",[14,15,17],"h2",{"id":16},"production-async-concurrent-http-with-rate-limiting-and-backoff","Production Async — Concurrent HTTP with Rate Limiting and Backoff",[19,20,22],"code-wrapper",{"language":21},"python",[23,24,28],"pre",{"className":25,"code":26,"language":21,"meta":27,"style":27},"language-python shiki shiki-themes github-light github-dark","# ── Production: async HTTP client with concurrency limiting, retry, and backoff ──\n# This is the pattern you'd use in a real async web scraper or API aggregator.\n\nimport asyncio\nimport random\nfrom typing import Any\n\nasync def fetch_with_retry(\n    url: str,\n    *,\n    max_retries: int = 3,\n    base_delay: float = 0.5,\n) -> dict[str, Any]:\n    \"\"\"Fetch a URL with exponential backoff retry — async-native, no blocking.\"\"\"\n    for attempt in range(1, max_retries + 1):\n        try:\n            # Simulate an async HTTP call — in production use aiohttp or httpx\n            await asyncio.sleep(random.uniform(0.05, 0.2))   # simulated network latency\n\n            if random.random() \u003C 0.3:   # 30% chance of transient failure\n                raise ConnectionError(f\"transient error on {url}\")\n\n            return {\"url\": url, \"status\": 200, \"attempt\": attempt}\n\n        except (ConnectionError, TimeoutError) as e:\n            if attempt >= max_retries:\n                raise   # re-raise after final attempt — caller handles\n            delay = base_delay * (2 ** (attempt - 1))   # exponential backoff: 0.5, 1.0, 2.0...\n            await asyncio.sleep(delay)\n\nasync def rate_limited_fetch(\n    urls: list[str],\n    max_concurrent: int = 10,\n) -> list[dict[str, Any]]:\n    \"\"\"Fetch many URLs with a concurrency cap — Semaphore prevents overwhelming the server.\"\"\"\n    semaphore = asyncio.Semaphore(max_concurrent)   # limits concurrent in-flight requests\n\n    async def guarded_fetch(url: str) -> dict[str, Any]:\n        async with semaphore:   # acquire — blocks if max_concurrent are already in flight\n            return await fetch_with_retry(url)\n\n    # gather runs ALL fetches concurrently, return_exceptions collects failures as values\n    results = await asyncio.gather(\n        *(guarded_fetch(url) for url in urls),\n        return_exceptions=True,   # exceptions returned as values, not raised — no silent loss\n    )\n\n    # Separate successes from failures for the caller\n    successes = [r for r in results if not isinstance(r, Exception)]\n    failures = [r for r in results if isinstance(r, Exception)]\n    print(f\"  {len(successes)} succeeded, {len(failures)} failed after retries\")\n    return successes\n\nasync def main():\n    urls = [f\"https:\u002F\u002Fapi.example.com\u002Fitem\u002F{i}\" for i in range(50)]\n\n    # Run all 50 fetches with at most 10 in flight at any time\n    results = await rate_limited_fetch(urls, max_concurrent=10)\n    print(f\"Fetched {len(results)} items successfully\")\n\nasyncio.run(main())\n","",[29,30,31,40,46,53,64,72,86,91,107,120,128,145,161,172,179,212,221,227,251,256,277,309,314,346,351,377,390,398,434,442,447,459,470,485,496,502,516,521,543,558,569,574,580,593,613,631,637,642,648,687,715,751,760,765,778,820,825,831,853,877,882],"code",{"__ignoreMap":27},[32,33,36],"span",{"class":34,"line":35},"line",1,[32,37,39],{"class":38},"sdCPZ","# ── Production: async HTTP client with concurrency limiting, retry, and backoff ──\n",[32,41,43],{"class":34,"line":42},2,[32,44,45],{"class":38},"# This is the pattern you'd use in a real async web scraper or API aggregator.\n",[32,47,49],{"class":34,"line":48},3,[32,50,52],{"emptyLinePlaceholder":51},true,"\n",[32,54,56,60],{"class":34,"line":55},4,[32,57,59],{"class":58},"svdQ7","import",[32,61,63],{"class":62},"ssxIu"," asyncio\n",[32,65,67,69],{"class":34,"line":66},5,[32,68,59],{"class":58},[32,70,71],{"class":62}," random\n",[32,73,75,78,81,83],{"class":34,"line":74},6,[32,76,77],{"class":58},"from",[32,79,80],{"class":62}," typing ",[32,82,59],{"class":58},[32,84,85],{"class":62}," Any\n",[32,87,89],{"class":34,"line":88},7,[32,90,52],{"emptyLinePlaceholder":51},[32,92,94,97,100,104],{"class":34,"line":93},8,[32,95,96],{"class":58},"async",[32,98,99],{"class":58}," def",[32,101,103],{"class":102},"sIsaT"," fetch_with_retry",[32,105,106],{"class":62},"(\n",[32,108,110,113,117],{"class":34,"line":109},9,[32,111,112],{"class":62},"    url: ",[32,114,116],{"class":115},"snvgF","str",[32,118,119],{"class":62},",\n",[32,121,123,126],{"class":34,"line":122},10,[32,124,125],{"class":58},"    *",[32,127,119],{"class":62},[32,129,131,134,137,140,143],{"class":34,"line":130},11,[32,132,133],{"class":62},"    max_retries: ",[32,135,136],{"class":115},"int",[32,138,139],{"class":58}," =",[32,141,142],{"class":115}," 3",[32,144,119],{"class":62},[32,146,148,151,154,156,159],{"class":34,"line":147},12,[32,149,150],{"class":62},"    base_delay: ",[32,152,153],{"class":115},"float",[32,155,139],{"class":58},[32,157,158],{"class":115}," 0.5",[32,160,119],{"class":62},[32,162,164,167,169],{"class":34,"line":163},13,[32,165,166],{"class":62},") -> dict[",[32,168,116],{"class":115},[32,170,171],{"class":62},", Any]:\n",[32,173,175],{"class":34,"line":174},14,[32,176,178],{"class":177},"sJ6F3","    \"\"\"Fetch a URL with exponential backoff retry — async-native, no blocking.\"\"\"\n",[32,180,182,185,188,191,194,197,200,203,206,209],{"class":34,"line":181},15,[32,183,184],{"class":58},"    for",[32,186,187],{"class":62}," attempt ",[32,189,190],{"class":58},"in",[32,192,193],{"class":115}," range",[32,195,196],{"class":62},"(",[32,198,199],{"class":115},"1",[32,201,202],{"class":62},", max_retries ",[32,204,205],{"class":58},"+",[32,207,208],{"class":115}," 1",[32,210,211],{"class":62},"):\n",[32,213,215,218],{"class":34,"line":214},16,[32,216,217],{"class":58},"        try",[32,219,220],{"class":62},":\n",[32,222,224],{"class":34,"line":223},17,[32,225,226],{"class":38},"            # Simulate an async HTTP call — in production use aiohttp or httpx\n",[32,228,230,233,236,239,242,245,248],{"class":34,"line":229},18,[32,231,232],{"class":58},"            await",[32,234,235],{"class":62}," asyncio.sleep(random.uniform(",[32,237,238],{"class":115},"0.05",[32,240,241],{"class":62},", ",[32,243,244],{"class":115},"0.2",[32,246,247],{"class":62},"))   ",[32,249,250],{"class":38},"# simulated network latency\n",[32,252,254],{"class":34,"line":253},19,[32,255,52],{"emptyLinePlaceholder":51},[32,257,259,262,265,268,271,274],{"class":34,"line":258},20,[32,260,261],{"class":58},"            if",[32,263,264],{"class":62}," random.random() ",[32,266,267],{"class":58},"\u003C",[32,269,270],{"class":115}," 0.3",[32,272,273],{"class":62},":   ",[32,275,276],{"class":38},"# 30% chance of transient failure\n",[32,278,280,283,286,288,291,294,297,300,303,306],{"class":34,"line":279},21,[32,281,282],{"class":58},"                raise",[32,284,285],{"class":115}," ConnectionError",[32,287,196],{"class":62},[32,289,290],{"class":58},"f",[32,292,293],{"class":177},"\"transient error on ",[32,295,296],{"class":115},"{",[32,298,299],{"class":62},"url",[32,301,302],{"class":115},"}",[32,304,305],{"class":177},"\"",[32,307,308],{"class":62},")\n",[32,310,312],{"class":34,"line":311},22,[32,313,52],{"emptyLinePlaceholder":51},[32,315,317,320,323,326,329,332,335,338,340,343],{"class":34,"line":316},23,[32,318,319],{"class":58},"            return",[32,321,322],{"class":62}," {",[32,324,325],{"class":177},"\"url\"",[32,327,328],{"class":62},": url, ",[32,330,331],{"class":177},"\"status\"",[32,333,334],{"class":62},": ",[32,336,337],{"class":115},"200",[32,339,241],{"class":62},[32,341,342],{"class":177},"\"attempt\"",[32,344,345],{"class":62},": attempt}\n",[32,347,349],{"class":34,"line":348},24,[32,350,52],{"emptyLinePlaceholder":51},[32,352,354,357,360,363,365,368,371,374],{"class":34,"line":353},25,[32,355,356],{"class":58},"        except",[32,358,359],{"class":62}," (",[32,361,362],{"class":115},"ConnectionError",[32,364,241],{"class":62},[32,366,367],{"class":115},"TimeoutError",[32,369,370],{"class":62},") ",[32,372,373],{"class":58},"as",[32,375,376],{"class":62}," e:\n",[32,378,380,382,384,387],{"class":34,"line":379},26,[32,381,261],{"class":58},[32,383,187],{"class":62},[32,385,386],{"class":58},">=",[32,388,389],{"class":62}," max_retries:\n",[32,391,393,395],{"class":34,"line":392},27,[32,394,282],{"class":58},[32,396,397],{"class":38},"   # re-raise after final attempt — caller handles\n",[32,399,401,404,407,410,413,415,418,421,424,427,429,431],{"class":34,"line":400},28,[32,402,403],{"class":62},"            delay ",[32,405,406],{"class":58},"=",[32,408,409],{"class":62}," base_delay ",[32,411,412],{"class":58},"*",[32,414,359],{"class":62},[32,416,417],{"class":115},"2",[32,419,420],{"class":58}," **",[32,422,423],{"class":62}," (attempt ",[32,425,426],{"class":58},"-",[32,428,208],{"class":115},[32,430,247],{"class":62},[32,432,433],{"class":38},"# exponential backoff: 0.5, 1.0, 2.0...\n",[32,435,437,439],{"class":34,"line":436},29,[32,438,232],{"class":58},[32,440,441],{"class":62}," asyncio.sleep(delay)\n",[32,443,445],{"class":34,"line":444},30,[32,446,52],{"emptyLinePlaceholder":51},[32,448,450,452,454,457],{"class":34,"line":449},31,[32,451,96],{"class":58},[32,453,99],{"class":58},[32,455,456],{"class":102}," rate_limited_fetch",[32,458,106],{"class":62},[32,460,462,465,467],{"class":34,"line":461},32,[32,463,464],{"class":62},"    urls: list[",[32,466,116],{"class":115},[32,468,469],{"class":62},"],\n",[32,471,473,476,478,480,483],{"class":34,"line":472},33,[32,474,475],{"class":62},"    max_concurrent: ",[32,477,136],{"class":115},[32,479,139],{"class":58},[32,481,482],{"class":115}," 10",[32,484,119],{"class":62},[32,486,488,491,493],{"class":34,"line":487},34,[32,489,490],{"class":62},") -> list[dict[",[32,492,116],{"class":115},[32,494,495],{"class":62},", Any]]:\n",[32,497,499],{"class":34,"line":498},35,[32,500,501],{"class":177},"    \"\"\"Fetch many URLs with a concurrency cap — Semaphore prevents overwhelming the server.\"\"\"\n",[32,503,505,508,510,513],{"class":34,"line":504},36,[32,506,507],{"class":62},"    semaphore ",[32,509,406],{"class":58},[32,511,512],{"class":62}," asyncio.Semaphore(max_concurrent)   ",[32,514,515],{"class":38},"# limits concurrent in-flight requests\n",[32,517,519],{"class":34,"line":518},37,[32,520,52],{"emptyLinePlaceholder":51},[32,522,524,527,529,532,535,537,539,541],{"class":34,"line":523},38,[32,525,526],{"class":58},"    async",[32,528,99],{"class":58},[32,530,531],{"class":102}," guarded_fetch",[32,533,534],{"class":62},"(url: ",[32,536,116],{"class":115},[32,538,166],{"class":62},[32,540,116],{"class":115},[32,542,171],{"class":62},[32,544,546,549,552,555],{"class":34,"line":545},39,[32,547,548],{"class":58},"        async",[32,550,551],{"class":58}," with",[32,553,554],{"class":62}," semaphore:   ",[32,556,557],{"class":38},"# acquire — blocks if max_concurrent are already in flight\n",[32,559,561,563,566],{"class":34,"line":560},40,[32,562,319],{"class":58},[32,564,565],{"class":58}," await",[32,567,568],{"class":62}," fetch_with_retry(url)\n",[32,570,572],{"class":34,"line":571},41,[32,573,52],{"emptyLinePlaceholder":51},[32,575,577],{"class":34,"line":576},42,[32,578,579],{"class":38},"    # gather runs ALL fetches concurrently, return_exceptions collects failures as values\n",[32,581,583,586,588,590],{"class":34,"line":582},43,[32,584,585],{"class":62},"    results ",[32,587,406],{"class":58},[32,589,565],{"class":58},[32,591,592],{"class":62}," asyncio.gather(\n",[32,594,596,599,602,605,608,610],{"class":34,"line":595},44,[32,597,598],{"class":58},"        *",[32,600,601],{"class":62},"(guarded_fetch(url) ",[32,603,604],{"class":58},"for",[32,606,607],{"class":62}," url ",[32,609,190],{"class":58},[32,611,612],{"class":62}," urls),\n",[32,614,616,620,622,625,628],{"class":34,"line":615},45,[32,617,619],{"class":618},"sCrzJ","        return_exceptions",[32,621,406],{"class":58},[32,623,624],{"class":115},"True",[32,626,627],{"class":62},",   ",[32,629,630],{"class":38},"# exceptions returned as values, not raised — no silent loss\n",[32,632,634],{"class":34,"line":633},46,[32,635,636],{"class":62},"    )\n",[32,638,640],{"class":34,"line":639},47,[32,641,52],{"emptyLinePlaceholder":51},[32,643,645],{"class":34,"line":644},48,[32,646,647],{"class":38},"    # Separate successes from failures for the caller\n",[32,649,651,654,656,659,661,664,666,669,672,675,678,681,684],{"class":34,"line":650},49,[32,652,653],{"class":62},"    successes ",[32,655,406],{"class":58},[32,657,658],{"class":62}," [r ",[32,660,604],{"class":58},[32,662,663],{"class":62}," r ",[32,665,190],{"class":58},[32,667,668],{"class":62}," results ",[32,670,671],{"class":58},"if",[32,673,674],{"class":58}," not",[32,676,677],{"class":115}," isinstance",[32,679,680],{"class":62},"(r, ",[32,682,683],{"class":115},"Exception",[32,685,686],{"class":62},")]\n",[32,688,690,693,695,697,699,701,703,705,707,709,711,713],{"class":34,"line":689},50,[32,691,692],{"class":62},"    failures ",[32,694,406],{"class":58},[32,696,658],{"class":62},[32,698,604],{"class":58},[32,700,663],{"class":62},[32,702,190],{"class":58},[32,704,668],{"class":62},[32,706,671],{"class":58},[32,708,677],{"class":115},[32,710,680],{"class":62},[32,712,683],{"class":115},[32,714,686],{"class":62},[32,716,718,721,723,725,728,731,734,736,739,741,744,746,749],{"class":34,"line":717},51,[32,719,720],{"class":115},"    print",[32,722,196],{"class":62},[32,724,290],{"class":58},[32,726,727],{"class":177},"\"  ",[32,729,730],{"class":115},"{len",[32,732,733],{"class":62},"(successes)",[32,735,302],{"class":115},[32,737,738],{"class":177}," succeeded, ",[32,740,730],{"class":115},[32,742,743],{"class":62},"(failures)",[32,745,302],{"class":115},[32,747,748],{"class":177}," failed after retries\"",[32,750,308],{"class":62},[32,752,754,757],{"class":34,"line":753},52,[32,755,756],{"class":58},"    return",[32,758,759],{"class":62}," successes\n",[32,761,763],{"class":34,"line":762},53,[32,764,52],{"emptyLinePlaceholder":51},[32,766,768,770,772,775],{"class":34,"line":767},54,[32,769,96],{"class":58},[32,771,99],{"class":58},[32,773,774],{"class":102}," main",[32,776,777],{"class":62},"():\n",[32,779,781,784,786,789,791,794,796,799,801,803,806,809,811,813,815,818],{"class":34,"line":780},55,[32,782,783],{"class":62},"    urls ",[32,785,406],{"class":58},[32,787,788],{"class":62}," [",[32,790,290],{"class":58},[32,792,793],{"class":177},"\"https:\u002F\u002Fapi.example.com\u002Fitem\u002F",[32,795,296],{"class":115},[32,797,798],{"class":62},"i",[32,800,302],{"class":115},[32,802,305],{"class":177},[32,804,805],{"class":58}," for",[32,807,808],{"class":62}," i ",[32,810,190],{"class":58},[32,812,193],{"class":115},[32,814,196],{"class":62},[32,816,817],{"class":115},"50",[32,819,686],{"class":62},[32,821,823],{"class":34,"line":822},56,[32,824,52],{"emptyLinePlaceholder":51},[32,826,828],{"class":34,"line":827},57,[32,829,830],{"class":38},"    # Run all 50 fetches with at most 10 in flight at any time\n",[32,832,834,836,838,840,843,846,848,851],{"class":34,"line":833},58,[32,835,585],{"class":62},[32,837,406],{"class":58},[32,839,565],{"class":58},[32,841,842],{"class":62}," rate_limited_fetch(urls, ",[32,844,845],{"class":618},"max_concurrent",[32,847,406],{"class":58},[32,849,850],{"class":115},"10",[32,852,308],{"class":62},[32,854,856,858,860,862,865,867,870,872,875],{"class":34,"line":855},59,[32,857,720],{"class":115},[32,859,196],{"class":62},[32,861,290],{"class":58},[32,863,864],{"class":177},"\"Fetched ",[32,866,730],{"class":115},[32,868,869],{"class":62},"(results)",[32,871,302],{"class":115},[32,873,874],{"class":177}," items successfully\"",[32,876,308],{"class":62},[32,878,880],{"class":34,"line":879},60,[32,881,52],{"emptyLinePlaceholder":51},[32,883,885],{"class":34,"line":884},61,[32,886,887],{"class":62},"asyncio.run(main())\n",[19,889,890],{"language":21},[23,891,893],{"className":25,"code":892,"language":21,"meta":27,"style":27},"# ── ANTI-PATTERN: blocking calls in async code freeze the ENTIRE event loop ──\n\nimport asyncio\nimport time\n\nasync def bad_async_fetch():\n    \"\"\"BLOCKING call — time.sleep() holds the thread, freezing ALL other coroutines.\"\"\"\n    time.sleep(2)    # the event loop can't switch to any other task during this\n    return \"done\"\n\nasync def good_async_fetch():\n    \"\"\"CORRECT — asyncio.sleep() yields control back to the event loop.\"\"\"\n    await asyncio.sleep(2)   # other coroutines can run during this wait\n    return \"done\"\n\n# ── When you MUST call blocking code: offload to a thread pool ──\nasync def fetch_with_blocking_lib():\n    \"\"\"For libraries without async support (e.g., requests, sync DB drivers).\"\"\"\n    # asyncio.to_thread runs a blocking function in a worker thread\n    # The event loop stays responsive while the thread blocks\n    result = await asyncio.to_thread(time.sleep, 2)   # blocking call in a thread\n    return result\n\n# ── Production: async context manager for resource lifecycle ──\nclass AsyncDBPool:\n    \"\"\"Async context manager for a database connection pool.\"\"\"\n    def __init__(self, dsn: str, pool_size: int = 10):\n        self.dsn = dsn\n        self.pool_size = pool_size\n        self._pool: asyncio.Queue = asyncio.Queue(pool_size)\n\n    async def __aenter__(self):\n        # __aenter__ is a coroutine — can await during setup\n        for _ in range(self.pool_size):\n            await self._pool.put({\"connection\": f\"conn-{_}\", \"dsn\": self.dsn})\n        return self\n\n    async def __aexit__(self, exc_type, exc_val, tb):\n        # Cleanup runs even if the body raised — close all connections\n        while not self._pool.empty():\n            conn = await self._pool.get()\n            # await conn.close() — in production\n        return False   # don't suppress exceptions\n\n    async def acquire(self):\n        return await self._pool.get()\n\n    async def release(self, conn):\n        await self._pool.put(conn)\n\nasync def main():\n    async with AsyncDBPool(\"postgres:\u002F\u002Flocalhost\u002Fapp\", pool_size=5) as pool:\n        conn = await pool.acquire()\n        try:\n            # use connection\n            pass\n        finally:\n            await pool.release(conn)\n    # pool is cleaned up here, even if the body raised\n\nasyncio.run(main())\n",[29,894,895,900,904,910,917,921,932,937,950,957,961,972,977,993,999,1003,1008,1019,1024,1029,1034,1053,1060,1064,1069,1079,1084,1108,1121,1133,1145,1149,1161,1166,1186,1227,1235,1239,1251,1256,1268,1282,1287,1297,1301,1312,1322,1326,1338,1348,1352,1362,1391,1403,1409,1414,1419,1426,1433,1438,1442],{"__ignoreMap":27},[32,896,897],{"class":34,"line":35},[32,898,899],{"class":38},"# ── ANTI-PATTERN: blocking calls in async code freeze the ENTIRE event loop ──\n",[32,901,902],{"class":34,"line":42},[32,903,52],{"emptyLinePlaceholder":51},[32,905,906,908],{"class":34,"line":48},[32,907,59],{"class":58},[32,909,63],{"class":62},[32,911,912,914],{"class":34,"line":55},[32,913,59],{"class":58},[32,915,916],{"class":62}," time\n",[32,918,919],{"class":34,"line":66},[32,920,52],{"emptyLinePlaceholder":51},[32,922,923,925,927,930],{"class":34,"line":74},[32,924,96],{"class":58},[32,926,99],{"class":58},[32,928,929],{"class":102}," bad_async_fetch",[32,931,777],{"class":62},[32,933,934],{"class":34,"line":88},[32,935,936],{"class":177},"    \"\"\"BLOCKING call — time.sleep() holds the thread, freezing ALL other coroutines.\"\"\"\n",[32,938,939,942,944,947],{"class":34,"line":93},[32,940,941],{"class":62},"    time.sleep(",[32,943,417],{"class":115},[32,945,946],{"class":62},")    ",[32,948,949],{"class":38},"# the event loop can't switch to any other task during this\n",[32,951,952,954],{"class":34,"line":109},[32,953,756],{"class":58},[32,955,956],{"class":177}," \"done\"\n",[32,958,959],{"class":34,"line":122},[32,960,52],{"emptyLinePlaceholder":51},[32,962,963,965,967,970],{"class":34,"line":130},[32,964,96],{"class":58},[32,966,99],{"class":58},[32,968,969],{"class":102}," good_async_fetch",[32,971,777],{"class":62},[32,973,974],{"class":34,"line":147},[32,975,976],{"class":177},"    \"\"\"CORRECT — asyncio.sleep() yields control back to the event loop.\"\"\"\n",[32,978,979,982,985,987,990],{"class":34,"line":163},[32,980,981],{"class":58},"    await",[32,983,984],{"class":62}," asyncio.sleep(",[32,986,417],{"class":115},[32,988,989],{"class":62},")   ",[32,991,992],{"class":38},"# other coroutines can run during this wait\n",[32,994,995,997],{"class":34,"line":174},[32,996,756],{"class":58},[32,998,956],{"class":177},[32,1000,1001],{"class":34,"line":181},[32,1002,52],{"emptyLinePlaceholder":51},[32,1004,1005],{"class":34,"line":214},[32,1006,1007],{"class":38},"# ── When you MUST call blocking code: offload to a thread pool ──\n",[32,1009,1010,1012,1014,1017],{"class":34,"line":223},[32,1011,96],{"class":58},[32,1013,99],{"class":58},[32,1015,1016],{"class":102}," fetch_with_blocking_lib",[32,1018,777],{"class":62},[32,1020,1021],{"class":34,"line":229},[32,1022,1023],{"class":177},"    \"\"\"For libraries without async support (e.g., requests, sync DB drivers).\"\"\"\n",[32,1025,1026],{"class":34,"line":253},[32,1027,1028],{"class":38},"    # asyncio.to_thread runs a blocking function in a worker thread\n",[32,1030,1031],{"class":34,"line":258},[32,1032,1033],{"class":38},"    # The event loop stays responsive while the thread blocks\n",[32,1035,1036,1039,1041,1043,1046,1048,1050],{"class":34,"line":279},[32,1037,1038],{"class":62},"    result ",[32,1040,406],{"class":58},[32,1042,565],{"class":58},[32,1044,1045],{"class":62}," asyncio.to_thread(time.sleep, ",[32,1047,417],{"class":115},[32,1049,989],{"class":62},[32,1051,1052],{"class":38},"# blocking call in a thread\n",[32,1054,1055,1057],{"class":34,"line":311},[32,1056,756],{"class":58},[32,1058,1059],{"class":62}," result\n",[32,1061,1062],{"class":34,"line":316},[32,1063,52],{"emptyLinePlaceholder":51},[32,1065,1066],{"class":34,"line":348},[32,1067,1068],{"class":38},"# ── Production: async context manager for resource lifecycle ──\n",[32,1070,1071,1074,1077],{"class":34,"line":353},[32,1072,1073],{"class":58},"class",[32,1075,1076],{"class":102}," AsyncDBPool",[32,1078,220],{"class":62},[32,1080,1081],{"class":34,"line":379},[32,1082,1083],{"class":177},"    \"\"\"Async context manager for a database connection pool.\"\"\"\n",[32,1085,1086,1089,1092,1095,1097,1100,1102,1104,1106],{"class":34,"line":392},[32,1087,1088],{"class":58},"    def",[32,1090,1091],{"class":115}," __init__",[32,1093,1094],{"class":62},"(self, dsn: ",[32,1096,116],{"class":115},[32,1098,1099],{"class":62},", pool_size: ",[32,1101,136],{"class":115},[32,1103,139],{"class":58},[32,1105,482],{"class":115},[32,1107,211],{"class":62},[32,1109,1110,1113,1116,1118],{"class":34,"line":400},[32,1111,1112],{"class":115},"        self",[32,1114,1115],{"class":62},".dsn ",[32,1117,406],{"class":58},[32,1119,1120],{"class":62}," dsn\n",[32,1122,1123,1125,1128,1130],{"class":34,"line":436},[32,1124,1112],{"class":115},[32,1126,1127],{"class":62},".pool_size ",[32,1129,406],{"class":58},[32,1131,1132],{"class":62}," pool_size\n",[32,1134,1135,1137,1140,1142],{"class":34,"line":444},[32,1136,1112],{"class":115},[32,1138,1139],{"class":62},"._pool: asyncio.Queue ",[32,1141,406],{"class":58},[32,1143,1144],{"class":62}," asyncio.Queue(pool_size)\n",[32,1146,1147],{"class":34,"line":449},[32,1148,52],{"emptyLinePlaceholder":51},[32,1150,1151,1153,1155,1158],{"class":34,"line":461},[32,1152,526],{"class":58},[32,1154,99],{"class":58},[32,1156,1157],{"class":115}," __aenter__",[32,1159,1160],{"class":62},"(self):\n",[32,1162,1163],{"class":34,"line":472},[32,1164,1165],{"class":38},"        # __aenter__ is a coroutine — can await during setup\n",[32,1167,1168,1171,1174,1176,1178,1180,1183],{"class":34,"line":487},[32,1169,1170],{"class":58},"        for",[32,1172,1173],{"class":62}," _ ",[32,1175,190],{"class":58},[32,1177,193],{"class":115},[32,1179,196],{"class":62},[32,1181,1182],{"class":115},"self",[32,1184,1185],{"class":62},".pool_size):\n",[32,1187,1188,1190,1193,1196,1199,1201,1203,1206,1208,1211,1213,1215,1217,1220,1222,1224],{"class":34,"line":498},[32,1189,232],{"class":58},[32,1191,1192],{"class":115}," self",[32,1194,1195],{"class":62},"._pool.put({",[32,1197,1198],{"class":177},"\"connection\"",[32,1200,334],{"class":62},[32,1202,290],{"class":58},[32,1204,1205],{"class":177},"\"conn-",[32,1207,296],{"class":115},[32,1209,1210],{"class":62},"_",[32,1212,302],{"class":115},[32,1214,305],{"class":177},[32,1216,241],{"class":62},[32,1218,1219],{"class":177},"\"dsn\"",[32,1221,334],{"class":62},[32,1223,1182],{"class":115},[32,1225,1226],{"class":62},".dsn})\n",[32,1228,1229,1232],{"class":34,"line":504},[32,1230,1231],{"class":58},"        return",[32,1233,1234],{"class":115}," self\n",[32,1236,1237],{"class":34,"line":518},[32,1238,52],{"emptyLinePlaceholder":51},[32,1240,1241,1243,1245,1248],{"class":34,"line":523},[32,1242,526],{"class":58},[32,1244,99],{"class":58},[32,1246,1247],{"class":115}," __aexit__",[32,1249,1250],{"class":62},"(self, exc_type, exc_val, tb):\n",[32,1252,1253],{"class":34,"line":545},[32,1254,1255],{"class":38},"        # Cleanup runs even if the body raised — close all connections\n",[32,1257,1258,1261,1263,1265],{"class":34,"line":560},[32,1259,1260],{"class":58},"        while",[32,1262,674],{"class":58},[32,1264,1192],{"class":115},[32,1266,1267],{"class":62},"._pool.empty():\n",[32,1269,1270,1273,1275,1277,1279],{"class":34,"line":571},[32,1271,1272],{"class":62},"            conn ",[32,1274,406],{"class":58},[32,1276,565],{"class":58},[32,1278,1192],{"class":115},[32,1280,1281],{"class":62},"._pool.get()\n",[32,1283,1284],{"class":34,"line":576},[32,1285,1286],{"class":38},"            # await conn.close() — in production\n",[32,1288,1289,1291,1294],{"class":34,"line":582},[32,1290,1231],{"class":58},[32,1292,1293],{"class":115}," False",[32,1295,1296],{"class":38},"   # don't suppress exceptions\n",[32,1298,1299],{"class":34,"line":595},[32,1300,52],{"emptyLinePlaceholder":51},[32,1302,1303,1305,1307,1310],{"class":34,"line":615},[32,1304,526],{"class":58},[32,1306,99],{"class":58},[32,1308,1309],{"class":102}," acquire",[32,1311,1160],{"class":62},[32,1313,1314,1316,1318,1320],{"class":34,"line":633},[32,1315,1231],{"class":58},[32,1317,565],{"class":58},[32,1319,1192],{"class":115},[32,1321,1281],{"class":62},[32,1323,1324],{"class":34,"line":639},[32,1325,52],{"emptyLinePlaceholder":51},[32,1327,1328,1330,1332,1335],{"class":34,"line":644},[32,1329,526],{"class":58},[32,1331,99],{"class":58},[32,1333,1334],{"class":102}," release",[32,1336,1337],{"class":62},"(self, conn):\n",[32,1339,1340,1343,1345],{"class":34,"line":650},[32,1341,1342],{"class":58},"        await",[32,1344,1192],{"class":115},[32,1346,1347],{"class":62},"._pool.put(conn)\n",[32,1349,1350],{"class":34,"line":689},[32,1351,52],{"emptyLinePlaceholder":51},[32,1353,1354,1356,1358,1360],{"class":34,"line":717},[32,1355,96],{"class":58},[32,1357,99],{"class":58},[32,1359,774],{"class":102},[32,1361,777],{"class":62},[32,1363,1364,1366,1368,1371,1374,1376,1379,1381,1384,1386,1388],{"class":34,"line":753},[32,1365,526],{"class":58},[32,1367,551],{"class":58},[32,1369,1370],{"class":62}," AsyncDBPool(",[32,1372,1373],{"class":177},"\"postgres:\u002F\u002Flocalhost\u002Fapp\"",[32,1375,241],{"class":62},[32,1377,1378],{"class":618},"pool_size",[32,1380,406],{"class":58},[32,1382,1383],{"class":115},"5",[32,1385,370],{"class":62},[32,1387,373],{"class":58},[32,1389,1390],{"class":62}," pool:\n",[32,1392,1393,1396,1398,1400],{"class":34,"line":762},[32,1394,1395],{"class":62},"        conn ",[32,1397,406],{"class":58},[32,1399,565],{"class":58},[32,1401,1402],{"class":62}," pool.acquire()\n",[32,1404,1405,1407],{"class":34,"line":767},[32,1406,217],{"class":58},[32,1408,220],{"class":62},[32,1410,1411],{"class":34,"line":780},[32,1412,1413],{"class":38},"            # use connection\n",[32,1415,1416],{"class":34,"line":822},[32,1417,1418],{"class":58},"            pass\n",[32,1420,1421,1424],{"class":34,"line":827},[32,1422,1423],{"class":58},"        finally",[32,1425,220],{"class":62},[32,1427,1428,1430],{"class":34,"line":833},[32,1429,232],{"class":58},[32,1431,1432],{"class":62}," pool.release(conn)\n",[32,1434,1435],{"class":34,"line":855},[32,1436,1437],{"class":38},"    # pool is cleaned up here, even if the body raised\n",[32,1439,1440],{"class":34,"line":879},[32,1441,52],{"emptyLinePlaceholder":51},[32,1443,1444],{"class":34,"line":884},[32,1445,887],{"class":62},[14,1447,1449],{"id":1448},"coroutines-vs-tasks-scheduling-and-concurrency","Coroutines vs Tasks: Scheduling and Concurrency",[19,1451,1452],{"language":21},[23,1453,1455],{"className":25,"code":1454,"language":21,"meta":27,"style":27},"import asyncio\n\nasync def worker(name, delay):\n    await asyncio.sleep(delay)\n    print(f\"{name} finished\")\n    return name\n\nasync def sequential():\n    # awaiting coroutines directly runs them ONE AT A TIME — no concurrency at all\n    await worker(\"A\", 1)\n    await worker(\"B\", 1)\n    # total: ~2 seconds\n\nasync def concurrent_tasks():\n    # wrapping in create_task() schedules BOTH immediately, running concurrently\n    task_a = asyncio.create_task(worker(\"A\", 1))\n    task_b = asyncio.create_task(worker(\"B\", 1))\n    await task_a\n    await task_b\n    # total: ~1 second — both tasks were running \"at the same time\" from the moment create_task() was called\n\nasyncio.run(concurrent_tasks())\n",[29,1456,1457,1463,1467,1479,1485,1507,1514,1518,1529,1534,1550,1565,1570,1574,1585,1590,1609,1626,1633,1640,1645,1649],{"__ignoreMap":27},[32,1458,1459,1461],{"class":34,"line":35},[32,1460,59],{"class":58},[32,1462,63],{"class":62},[32,1464,1465],{"class":34,"line":42},[32,1466,52],{"emptyLinePlaceholder":51},[32,1468,1469,1471,1473,1476],{"class":34,"line":48},[32,1470,96],{"class":58},[32,1472,99],{"class":58},[32,1474,1475],{"class":102}," worker",[32,1477,1478],{"class":62},"(name, delay):\n",[32,1480,1481,1483],{"class":34,"line":55},[32,1482,981],{"class":58},[32,1484,441],{"class":62},[32,1486,1487,1489,1491,1493,1495,1497,1500,1502,1505],{"class":34,"line":66},[32,1488,720],{"class":115},[32,1490,196],{"class":62},[32,1492,290],{"class":58},[32,1494,305],{"class":177},[32,1496,296],{"class":115},[32,1498,1499],{"class":62},"name",[32,1501,302],{"class":115},[32,1503,1504],{"class":177}," finished\"",[32,1506,308],{"class":62},[32,1508,1509,1511],{"class":34,"line":74},[32,1510,756],{"class":58},[32,1512,1513],{"class":62}," name\n",[32,1515,1516],{"class":34,"line":88},[32,1517,52],{"emptyLinePlaceholder":51},[32,1519,1520,1522,1524,1527],{"class":34,"line":93},[32,1521,96],{"class":58},[32,1523,99],{"class":58},[32,1525,1526],{"class":102}," sequential",[32,1528,777],{"class":62},[32,1530,1531],{"class":34,"line":109},[32,1532,1533],{"class":38},"    # awaiting coroutines directly runs them ONE AT A TIME — no concurrency at all\n",[32,1535,1536,1538,1541,1544,1546,1548],{"class":34,"line":122},[32,1537,981],{"class":58},[32,1539,1540],{"class":62}," worker(",[32,1542,1543],{"class":177},"\"A\"",[32,1545,241],{"class":62},[32,1547,199],{"class":115},[32,1549,308],{"class":62},[32,1551,1552,1554,1556,1559,1561,1563],{"class":34,"line":130},[32,1553,981],{"class":58},[32,1555,1540],{"class":62},[32,1557,1558],{"class":177},"\"B\"",[32,1560,241],{"class":62},[32,1562,199],{"class":115},[32,1564,308],{"class":62},[32,1566,1567],{"class":34,"line":147},[32,1568,1569],{"class":38},"    # total: ~2 seconds\n",[32,1571,1572],{"class":34,"line":163},[32,1573,52],{"emptyLinePlaceholder":51},[32,1575,1576,1578,1580,1583],{"class":34,"line":174},[32,1577,96],{"class":58},[32,1579,99],{"class":58},[32,1581,1582],{"class":102}," concurrent_tasks",[32,1584,777],{"class":62},[32,1586,1587],{"class":34,"line":181},[32,1588,1589],{"class":38},"    # wrapping in create_task() schedules BOTH immediately, running concurrently\n",[32,1591,1592,1595,1597,1600,1602,1604,1606],{"class":34,"line":214},[32,1593,1594],{"class":62},"    task_a ",[32,1596,406],{"class":58},[32,1598,1599],{"class":62}," asyncio.create_task(worker(",[32,1601,1543],{"class":177},[32,1603,241],{"class":62},[32,1605,199],{"class":115},[32,1607,1608],{"class":62},"))\n",[32,1610,1611,1614,1616,1618,1620,1622,1624],{"class":34,"line":223},[32,1612,1613],{"class":62},"    task_b ",[32,1615,406],{"class":58},[32,1617,1599],{"class":62},[32,1619,1558],{"class":177},[32,1621,241],{"class":62},[32,1623,199],{"class":115},[32,1625,1608],{"class":62},[32,1627,1628,1630],{"class":34,"line":229},[32,1629,981],{"class":58},[32,1631,1632],{"class":62}," task_a\n",[32,1634,1635,1637],{"class":34,"line":253},[32,1636,981],{"class":58},[32,1638,1639],{"class":62}," task_b\n",[32,1641,1642],{"class":34,"line":258},[32,1643,1644],{"class":38},"    # total: ~1 second — both tasks were running \"at the same time\" from the moment create_task() was called\n",[32,1646,1647],{"class":34,"line":279},[32,1648,52],{"emptyLinePlaceholder":51},[32,1650,1651],{"class":34,"line":311},[32,1652,1653],{"class":62},"asyncio.run(concurrent_tasks())\n",[1655,1656,1657,1658,1664,1665,1668,1669,1672,1673,1676,1677,1680],"p",{},"A bare coroutine, awaited directly, blocks the awaiting coroutine until it finishes — sequential, not concurrent. A ",[1659,1660,1661],"strong",{},[29,1662,1663],{},"Task"," (created via ",[29,1666,1667],{},"asyncio.create_task()",") is scheduled on the event loop immediately and runs independently in the background; ",[29,1670,1671],{},"await","-ing a task later just waits for a result that may already be in progress (or done). ",[1659,1674,1675],{},"This distinction is the single biggest performance gotcha in async code",": writing ",[29,1678,1679],{},"await coro1(); await coro2()"," when you meant \"run both at once\" silently serializes work that should have overlapped.",[14,1682,1684,1687],{"id":1683},"asynciogather-running-many-coroutines-concurrently",[29,1685,1686],{},"asyncio.gather"," — Running Many Coroutines Concurrently",[19,1689,1690],{"language":21},[23,1691,1693],{"className":25,"code":1692,"language":21,"meta":27,"style":27},"import asyncio\n\nasync def fetch(id_, fail=False):\n    await asyncio.sleep(0.1)\n    if fail:\n        raise ValueError(f\"fetch {id_} failed\")\n    return id_\n\nasync def main():\n    # DEFAULT: gather propagates the FIRST exception immediately, cancelling nothing else automatically\n    try:\n        results = await asyncio.gather(fetch(1), fetch(2, fail=True), fetch(3))\n    except ValueError as e:\n        print(f\"gather raised: {e}\")   # only ONE exception surfaces, even though only task 2 failed\n\n    # return_exceptions=True — collects EVERY result\u002Fexception instead of raising immediately\n    results = await asyncio.gather(fetch(1), fetch(2, fail=True), fetch(3), return_exceptions=True)\n    print(results)   # [1, ValueError('fetch 2 failed'), 3] — exceptions returned as VALUES, not raised\n\nasyncio.run(main())\n",[29,1694,1695,1701,1705,1724,1735,1743,1770,1777,1781,1791,1796,1803,1838,1850,1876,1880,1885,1925,1935,1939],{"__ignoreMap":27},[32,1696,1697,1699],{"class":34,"line":35},[32,1698,59],{"class":58},[32,1700,63],{"class":62},[32,1702,1703],{"class":34,"line":42},[32,1704,52],{"emptyLinePlaceholder":51},[32,1706,1707,1709,1711,1714,1717,1719,1722],{"class":34,"line":48},[32,1708,96],{"class":58},[32,1710,99],{"class":58},[32,1712,1713],{"class":102}," fetch",[32,1715,1716],{"class":62},"(id_, fail",[32,1718,406],{"class":58},[32,1720,1721],{"class":115},"False",[32,1723,211],{"class":62},[32,1725,1726,1728,1730,1733],{"class":34,"line":55},[32,1727,981],{"class":58},[32,1729,984],{"class":62},[32,1731,1732],{"class":115},"0.1",[32,1734,308],{"class":62},[32,1736,1737,1740],{"class":34,"line":66},[32,1738,1739],{"class":58},"    if",[32,1741,1742],{"class":62}," fail:\n",[32,1744,1745,1748,1751,1753,1755,1758,1760,1763,1765,1768],{"class":34,"line":74},[32,1746,1747],{"class":58},"        raise",[32,1749,1750],{"class":115}," ValueError",[32,1752,196],{"class":62},[32,1754,290],{"class":58},[32,1756,1757],{"class":177},"\"fetch ",[32,1759,296],{"class":115},[32,1761,1762],{"class":62},"id_",[32,1764,302],{"class":115},[32,1766,1767],{"class":177}," failed\"",[32,1769,308],{"class":62},[32,1771,1772,1774],{"class":34,"line":88},[32,1773,756],{"class":58},[32,1775,1776],{"class":62}," id_\n",[32,1778,1779],{"class":34,"line":93},[32,1780,52],{"emptyLinePlaceholder":51},[32,1782,1783,1785,1787,1789],{"class":34,"line":109},[32,1784,96],{"class":58},[32,1786,99],{"class":58},[32,1788,774],{"class":102},[32,1790,777],{"class":62},[32,1792,1793],{"class":34,"line":122},[32,1794,1795],{"class":38},"    # DEFAULT: gather propagates the FIRST exception immediately, cancelling nothing else automatically\n",[32,1797,1798,1801],{"class":34,"line":130},[32,1799,1800],{"class":58},"    try",[32,1802,220],{"class":62},[32,1804,1805,1808,1810,1812,1815,1817,1820,1822,1824,1827,1829,1831,1833,1836],{"class":34,"line":147},[32,1806,1807],{"class":62},"        results ",[32,1809,406],{"class":58},[32,1811,565],{"class":58},[32,1813,1814],{"class":62}," asyncio.gather(fetch(",[32,1816,199],{"class":115},[32,1818,1819],{"class":62},"), fetch(",[32,1821,417],{"class":115},[32,1823,241],{"class":62},[32,1825,1826],{"class":618},"fail",[32,1828,406],{"class":58},[32,1830,624],{"class":115},[32,1832,1819],{"class":62},[32,1834,1835],{"class":115},"3",[32,1837,1608],{"class":62},[32,1839,1840,1843,1845,1848],{"class":34,"line":163},[32,1841,1842],{"class":58},"    except",[32,1844,1750],{"class":115},[32,1846,1847],{"class":58}," as",[32,1849,376],{"class":62},[32,1851,1852,1855,1857,1859,1862,1864,1867,1869,1871,1873],{"class":34,"line":174},[32,1853,1854],{"class":115},"        print",[32,1856,196],{"class":62},[32,1858,290],{"class":58},[32,1860,1861],{"class":177},"\"gather raised: ",[32,1863,296],{"class":115},[32,1865,1866],{"class":62},"e",[32,1868,302],{"class":115},[32,1870,305],{"class":177},[32,1872,989],{"class":62},[32,1874,1875],{"class":38},"# only ONE exception surfaces, even though only task 2 failed\n",[32,1877,1878],{"class":34,"line":181},[32,1879,52],{"emptyLinePlaceholder":51},[32,1881,1882],{"class":34,"line":214},[32,1883,1884],{"class":38},"    # return_exceptions=True — collects EVERY result\u002Fexception instead of raising immediately\n",[32,1886,1887,1889,1891,1893,1895,1897,1899,1901,1903,1905,1907,1909,1911,1913,1916,1919,1921,1923],{"class":34,"line":223},[32,1888,585],{"class":62},[32,1890,406],{"class":58},[32,1892,565],{"class":58},[32,1894,1814],{"class":62},[32,1896,199],{"class":115},[32,1898,1819],{"class":62},[32,1900,417],{"class":115},[32,1902,241],{"class":62},[32,1904,1826],{"class":618},[32,1906,406],{"class":58},[32,1908,624],{"class":115},[32,1910,1819],{"class":62},[32,1912,1835],{"class":115},[32,1914,1915],{"class":62},"), ",[32,1917,1918],{"class":618},"return_exceptions",[32,1920,406],{"class":58},[32,1922,624],{"class":115},[32,1924,308],{"class":62},[32,1926,1927,1929,1932],{"class":34,"line":229},[32,1928,720],{"class":115},[32,1930,1931],{"class":62},"(results)   ",[32,1933,1934],{"class":38},"# [1, ValueError('fetch 2 failed'), 3] — exceptions returned as VALUES, not raised\n",[32,1936,1937],{"class":34,"line":253},[32,1938,52],{"emptyLinePlaceholder":51},[32,1940,1941],{"class":34,"line":258},[32,1942,887],{"class":62},[1655,1944,1945,1946,241,1949,1952,1953,1957,1958,1961],{},"Without ",[29,1947,1948],{},"return_exceptions=True",[29,1950,1951],{},"gather"," raises the first exception it encounters, but the ",[1954,1955,1956],"em",{},"other"," coroutines are ",[1659,1959,1960],{},"not automatically cancelled"," — they keep running in the background, and their results (or further exceptions) are simply discarded, which can silently mask a fire-and-forget failure or leave background work running longer than expected.",[14,1963,1965,1968,1969],{"id":1964},"async-with-and-async-for",[29,1966,1967],{},"async with"," and ",[29,1970,1971],{},"async for",[19,1973,1974],{"language":21},[23,1975,1977],{"className":25,"code":1976,"language":21,"meta":27,"style":27},"import asyncio\n\nclass AsyncResource:\n    async def __aenter__(self):\n        print(\"acquiring (async)\")\n        await asyncio.sleep(0.1)\n        return self\n\n    async def __aexit__(self, exc_type, exc_value, tb):\n        print(\"releasing (async)\")\n        await asyncio.sleep(0.1)\n        return False\n\nasync def main():\n    async with AsyncResource() as resource:\n        print(\"using resource\")\n\nasyncio.run(main())\n",[29,1978,1979,1985,1989,1998,2008,2019,2029,2035,2039,2050,2061,2071,2078,2082,2092,2106,2117,2121],{"__ignoreMap":27},[32,1980,1981,1983],{"class":34,"line":35},[32,1982,59],{"class":58},[32,1984,63],{"class":62},[32,1986,1987],{"class":34,"line":42},[32,1988,52],{"emptyLinePlaceholder":51},[32,1990,1991,1993,1996],{"class":34,"line":48},[32,1992,1073],{"class":58},[32,1994,1995],{"class":102}," AsyncResource",[32,1997,220],{"class":62},[32,1999,2000,2002,2004,2006],{"class":34,"line":55},[32,2001,526],{"class":58},[32,2003,99],{"class":58},[32,2005,1157],{"class":115},[32,2007,1160],{"class":62},[32,2009,2010,2012,2014,2017],{"class":34,"line":66},[32,2011,1854],{"class":115},[32,2013,196],{"class":62},[32,2015,2016],{"class":177},"\"acquiring (async)\"",[32,2018,308],{"class":62},[32,2020,2021,2023,2025,2027],{"class":34,"line":74},[32,2022,1342],{"class":58},[32,2024,984],{"class":62},[32,2026,1732],{"class":115},[32,2028,308],{"class":62},[32,2030,2031,2033],{"class":34,"line":88},[32,2032,1231],{"class":58},[32,2034,1234],{"class":115},[32,2036,2037],{"class":34,"line":93},[32,2038,52],{"emptyLinePlaceholder":51},[32,2040,2041,2043,2045,2047],{"class":34,"line":109},[32,2042,526],{"class":58},[32,2044,99],{"class":58},[32,2046,1247],{"class":115},[32,2048,2049],{"class":62},"(self, exc_type, exc_value, tb):\n",[32,2051,2052,2054,2056,2059],{"class":34,"line":122},[32,2053,1854],{"class":115},[32,2055,196],{"class":62},[32,2057,2058],{"class":177},"\"releasing (async)\"",[32,2060,308],{"class":62},[32,2062,2063,2065,2067,2069],{"class":34,"line":130},[32,2064,1342],{"class":58},[32,2066,984],{"class":62},[32,2068,1732],{"class":115},[32,2070,308],{"class":62},[32,2072,2073,2075],{"class":34,"line":147},[32,2074,1231],{"class":58},[32,2076,2077],{"class":115}," False\n",[32,2079,2080],{"class":34,"line":163},[32,2081,52],{"emptyLinePlaceholder":51},[32,2083,2084,2086,2088,2090],{"class":34,"line":174},[32,2085,96],{"class":58},[32,2087,99],{"class":58},[32,2089,774],{"class":102},[32,2091,777],{"class":62},[32,2093,2094,2096,2098,2101,2103],{"class":34,"line":181},[32,2095,526],{"class":58},[32,2097,551],{"class":58},[32,2099,2100],{"class":62}," AsyncResource() ",[32,2102,373],{"class":58},[32,2104,2105],{"class":62}," resource:\n",[32,2107,2108,2110,2112,2115],{"class":34,"line":214},[32,2109,1854],{"class":115},[32,2111,196],{"class":62},[32,2113,2114],{"class":177},"\"using resource\"",[32,2116,308],{"class":62},[32,2118,2119],{"class":34,"line":223},[32,2120,52],{"emptyLinePlaceholder":51},[32,2122,2123],{"class":34,"line":229},[32,2124,887],{"class":62},[1655,2126,2127,2130,2131,2134,2135,2130,2138,2141,2142,2144],{},[29,2128,2129],{},"__aenter__","\u002F",[29,2132,2133],{},"__aexit__"," mirror ",[29,2136,2137],{},"__enter__",[29,2139,2140],{},"__exit__"," from chapter 14 exactly, except both are coroutines — necessary any time acquiring or releasing a resource itself requires an ",[29,2143,1671],{}," (e.g., an async database connection pool). The same pattern applies to iteration:",[19,2146,2147],{"language":21},[23,2148,2150],{"className":25,"code":2149,"language":21,"meta":27,"style":27},"class AsyncRange:\n    def __init__(self, limit):\n        self.limit = limit\n        self.current = 0\n\n    def __aiter__(self):\n        return self\n\n    async def __anext__(self):\n        if self.current >= self.limit:\n            raise StopAsyncIteration\n        await asyncio.sleep(0.05)\n        value = self.current\n        self.current += 1\n        return value\n\nasync def main():\n    async for value in AsyncRange(3):\n        print(value)   # 0, 1, 2 — each with a real await between iterations\n\nasyncio.run(main())\n",[29,2151,2152,2161,2170,2182,2194,2198,2207,2213,2217,2228,2244,2252,2262,2274,2286,2293,2297,2307,2325,2335,2339],{"__ignoreMap":27},[32,2153,2154,2156,2159],{"class":34,"line":35},[32,2155,1073],{"class":58},[32,2157,2158],{"class":102}," AsyncRange",[32,2160,220],{"class":62},[32,2162,2163,2165,2167],{"class":34,"line":42},[32,2164,1088],{"class":58},[32,2166,1091],{"class":115},[32,2168,2169],{"class":62},"(self, limit):\n",[32,2171,2172,2174,2177,2179],{"class":34,"line":48},[32,2173,1112],{"class":115},[32,2175,2176],{"class":62},".limit ",[32,2178,406],{"class":58},[32,2180,2181],{"class":62}," limit\n",[32,2183,2184,2186,2189,2191],{"class":34,"line":55},[32,2185,1112],{"class":115},[32,2187,2188],{"class":62},".current ",[32,2190,406],{"class":58},[32,2192,2193],{"class":115}," 0\n",[32,2195,2196],{"class":34,"line":66},[32,2197,52],{"emptyLinePlaceholder":51},[32,2199,2200,2202,2205],{"class":34,"line":74},[32,2201,1088],{"class":58},[32,2203,2204],{"class":115}," __aiter__",[32,2206,1160],{"class":62},[32,2208,2209,2211],{"class":34,"line":88},[32,2210,1231],{"class":58},[32,2212,1234],{"class":115},[32,2214,2215],{"class":34,"line":93},[32,2216,52],{"emptyLinePlaceholder":51},[32,2218,2219,2221,2223,2226],{"class":34,"line":109},[32,2220,526],{"class":58},[32,2222,99],{"class":58},[32,2224,2225],{"class":115}," __anext__",[32,2227,1160],{"class":62},[32,2229,2230,2233,2235,2237,2239,2241],{"class":34,"line":122},[32,2231,2232],{"class":58},"        if",[32,2234,1192],{"class":115},[32,2236,2188],{"class":62},[32,2238,386],{"class":58},[32,2240,1192],{"class":115},[32,2242,2243],{"class":62},".limit:\n",[32,2245,2246,2249],{"class":34,"line":130},[32,2247,2248],{"class":58},"            raise",[32,2250,2251],{"class":115}," StopAsyncIteration\n",[32,2253,2254,2256,2258,2260],{"class":34,"line":147},[32,2255,1342],{"class":58},[32,2257,984],{"class":62},[32,2259,238],{"class":115},[32,2261,308],{"class":62},[32,2263,2264,2267,2269,2271],{"class":34,"line":163},[32,2265,2266],{"class":62},"        value ",[32,2268,406],{"class":58},[32,2270,1192],{"class":115},[32,2272,2273],{"class":62},".current\n",[32,2275,2276,2278,2280,2283],{"class":34,"line":174},[32,2277,1112],{"class":115},[32,2279,2188],{"class":62},[32,2281,2282],{"class":58},"+=",[32,2284,2285],{"class":115}," 1\n",[32,2287,2288,2290],{"class":34,"line":181},[32,2289,1231],{"class":58},[32,2291,2292],{"class":62}," value\n",[32,2294,2295],{"class":34,"line":214},[32,2296,52],{"emptyLinePlaceholder":51},[32,2298,2299,2301,2303,2305],{"class":34,"line":223},[32,2300,96],{"class":58},[32,2302,99],{"class":58},[32,2304,774],{"class":102},[32,2306,777],{"class":62},[32,2308,2309,2311,2313,2316,2318,2321,2323],{"class":34,"line":229},[32,2310,526],{"class":58},[32,2312,805],{"class":58},[32,2314,2315],{"class":62}," value ",[32,2317,190],{"class":58},[32,2319,2320],{"class":62}," AsyncRange(",[32,2322,1835],{"class":115},[32,2324,211],{"class":62},[32,2326,2327,2329,2332],{"class":34,"line":253},[32,2328,1854],{"class":115},[32,2330,2331],{"class":62},"(value)   ",[32,2333,2334],{"class":38},"# 0, 1, 2 — each with a real await between iterations\n",[32,2336,2337],{"class":34,"line":258},[32,2338,52],{"emptyLinePlaceholder":51},[32,2340,2341],{"class":34,"line":279},[32,2342,887],{"class":62},[14,2344,2346],{"id":2345},"async-generators","Async Generators",[19,2348,2349],{"language":21},[23,2350,2352],{"className":25,"code":2351,"language":21,"meta":27,"style":27},"async def fetch_pages(total):\n    for page in range(total):\n        await asyncio.sleep(0.1)     # simulate an async network call per page\n        yield f\"page-{page}\"\n\nasync def main():\n    async for page in fetch_pages(3):\n        print(page)\n\n    all_pages = [page async for page in fetch_pages(3)]   # async comprehension — note `async for`\n    print(all_pages)\n\nasyncio.run(main())\n",[29,2353,2354,2366,2379,2393,2414,2418,2428,2445,2452,2456,2484,2491,2495],{"__ignoreMap":27},[32,2355,2356,2358,2360,2363],{"class":34,"line":35},[32,2357,96],{"class":58},[32,2359,99],{"class":58},[32,2361,2362],{"class":102}," fetch_pages",[32,2364,2365],{"class":62},"(total):\n",[32,2367,2368,2370,2373,2375,2377],{"class":34,"line":42},[32,2369,184],{"class":58},[32,2371,2372],{"class":62}," page ",[32,2374,190],{"class":58},[32,2376,193],{"class":115},[32,2378,2365],{"class":62},[32,2380,2381,2383,2385,2387,2390],{"class":34,"line":48},[32,2382,1342],{"class":58},[32,2384,984],{"class":62},[32,2386,1732],{"class":115},[32,2388,2389],{"class":62},")     ",[32,2391,2392],{"class":38},"# simulate an async network call per page\n",[32,2394,2395,2398,2401,2404,2406,2409,2411],{"class":34,"line":55},[32,2396,2397],{"class":58},"        yield",[32,2399,2400],{"class":58}," f",[32,2402,2403],{"class":177},"\"page-",[32,2405,296],{"class":115},[32,2407,2408],{"class":62},"page",[32,2410,302],{"class":115},[32,2412,2413],{"class":177},"\"\n",[32,2415,2416],{"class":34,"line":66},[32,2417,52],{"emptyLinePlaceholder":51},[32,2419,2420,2422,2424,2426],{"class":34,"line":74},[32,2421,96],{"class":58},[32,2423,99],{"class":58},[32,2425,774],{"class":102},[32,2427,777],{"class":62},[32,2429,2430,2432,2434,2436,2438,2441,2443],{"class":34,"line":88},[32,2431,526],{"class":58},[32,2433,805],{"class":58},[32,2435,2372],{"class":62},[32,2437,190],{"class":58},[32,2439,2440],{"class":62}," fetch_pages(",[32,2442,1835],{"class":115},[32,2444,211],{"class":62},[32,2446,2447,2449],{"class":34,"line":93},[32,2448,1854],{"class":115},[32,2450,2451],{"class":62},"(page)\n",[32,2453,2454],{"class":34,"line":109},[32,2455,52],{"emptyLinePlaceholder":51},[32,2457,2458,2461,2463,2466,2468,2470,2472,2474,2476,2478,2481],{"class":34,"line":122},[32,2459,2460],{"class":62},"    all_pages ",[32,2462,406],{"class":58},[32,2464,2465],{"class":62}," [page ",[32,2467,96],{"class":58},[32,2469,805],{"class":58},[32,2471,2372],{"class":62},[32,2473,190],{"class":58},[32,2475,2440],{"class":62},[32,2477,1835],{"class":115},[32,2479,2480],{"class":62},")]   ",[32,2482,2483],{"class":38},"# async comprehension — note `async for`\n",[32,2485,2486,2488],{"class":34,"line":130},[32,2487,720],{"class":115},[32,2489,2490],{"class":62},"(all_pages)\n",[32,2492,2493],{"class":34,"line":147},[32,2494,52],{"emptyLinePlaceholder":51},[32,2496,2497],{"class":34,"line":163},[32,2498,887],{"class":62},[1655,2500,2501,2502,2505,2506,2509,2510,2513,2514,2516,2517,2519],{},"An ",[29,2503,2504],{},"async def"," function containing ",[29,2507,2508],{},"yield"," is an ",[1659,2511,2512],{},"async generator",", combining chapter 9's generator laziness with async's cooperative scheduling — each ",[29,2515,2508],{}," can be preceded by real ",[29,2518,1671],{},"s, ideal for streaming paginated API results without loading everything into memory upfront.",[14,2521,2523],{"id":2522},"common-pitfalls","Common Pitfalls",[2525,2526,2528],"h3",{"id":2527},"blocking-calls-inside-a-coroutine-freeze-the-entire-event-loop","Blocking calls inside a coroutine freeze the entire event loop",[19,2530,2531],{"language":21},[23,2532,2534],{"className":25,"code":2533,"language":21,"meta":27,"style":27},"import asyncio\nimport time\n\nasync def bad_worker(name):\n    print(f\"{name}: starting\")\n    time.sleep(2)              # BLOCKING call — freezes the ENTIRE event loop, not just this coroutine!\n    print(f\"{name}: done\")\n\nasync def main():\n    await asyncio.gather(bad_worker(\"A\"), bad_worker(\"B\"))\n    # takes ~4 seconds, NOT ~2 — time.sleep() blocks the whole thread, defeating async's entire purpose\n\nasyncio.run(main())\n",[29,2535,2536,2542,2548,2552,2564,2585,2597,2618,2622,2632,2648,2653,2657],{"__ignoreMap":27},[32,2537,2538,2540],{"class":34,"line":35},[32,2539,59],{"class":58},[32,2541,63],{"class":62},[32,2543,2544,2546],{"class":34,"line":42},[32,2545,59],{"class":58},[32,2547,916],{"class":62},[32,2549,2550],{"class":34,"line":48},[32,2551,52],{"emptyLinePlaceholder":51},[32,2553,2554,2556,2558,2561],{"class":34,"line":55},[32,2555,96],{"class":58},[32,2557,99],{"class":58},[32,2559,2560],{"class":102}," bad_worker",[32,2562,2563],{"class":62},"(name):\n",[32,2565,2566,2568,2570,2572,2574,2576,2578,2580,2583],{"class":34,"line":66},[32,2567,720],{"class":115},[32,2569,196],{"class":62},[32,2571,290],{"class":58},[32,2573,305],{"class":177},[32,2575,296],{"class":115},[32,2577,1499],{"class":62},[32,2579,302],{"class":115},[32,2581,2582],{"class":177},": starting\"",[32,2584,308],{"class":62},[32,2586,2587,2589,2591,2594],{"class":34,"line":74},[32,2588,941],{"class":62},[32,2590,417],{"class":115},[32,2592,2593],{"class":62},")              ",[32,2595,2596],{"class":38},"# BLOCKING call — freezes the ENTIRE event loop, not just this coroutine!\n",[32,2598,2599,2601,2603,2605,2607,2609,2611,2613,2616],{"class":34,"line":88},[32,2600,720],{"class":115},[32,2602,196],{"class":62},[32,2604,290],{"class":58},[32,2606,305],{"class":177},[32,2608,296],{"class":115},[32,2610,1499],{"class":62},[32,2612,302],{"class":115},[32,2614,2615],{"class":177},": done\"",[32,2617,308],{"class":62},[32,2619,2620],{"class":34,"line":93},[32,2621,52],{"emptyLinePlaceholder":51},[32,2623,2624,2626,2628,2630],{"class":34,"line":109},[32,2625,96],{"class":58},[32,2627,99],{"class":58},[32,2629,774],{"class":102},[32,2631,777],{"class":62},[32,2633,2634,2636,2639,2641,2644,2646],{"class":34,"line":122},[32,2635,981],{"class":58},[32,2637,2638],{"class":62}," asyncio.gather(bad_worker(",[32,2640,1543],{"class":177},[32,2642,2643],{"class":62},"), bad_worker(",[32,2645,1558],{"class":177},[32,2647,1608],{"class":62},[32,2649,2650],{"class":34,"line":130},[32,2651,2652],{"class":38},"    # takes ~4 seconds, NOT ~2 — time.sleep() blocks the whole thread, defeating async's entire purpose\n",[32,2654,2655],{"class":34,"line":147},[32,2656,52],{"emptyLinePlaceholder":51},[32,2658,2659],{"class":34,"line":163},[32,2660,887],{"class":62},[1655,2662,2663,2666,2667,2670,2671,2674,2675,241,2678,2130,2681,2684],{},[29,2664,2665],{},"time.sleep()",", synchronous ",[29,2668,2669],{},"requests.get()",", blocking file I\u002FO, and any other call that doesn't yield control back to the event loop will stall ",[1659,2672,2673],{},"every"," coroutine scheduled on that loop, not just the one that called it — because everything shares one thread. The fix is either an async-native library (",[29,2676,2677],{},"asyncio.sleep",[29,2679,2680],{},"aiohttp",[29,2682,2683],{},"httpx"," for HTTP) or explicitly offloading blocking work to a thread pool:",[19,2686,2687],{"language":21},[23,2688,2690],{"className":25,"code":2689,"language":21,"meta":27,"style":27},"import asyncio\nimport time\n\nasync def good_worker(name):\n    print(f\"{name}: starting\")\n    await asyncio.to_thread(time.sleep, 2)   # runs the blocking call in a worker thread, event loop stays free\n    print(f\"{name}: done\")\n\nasync def main():\n    await asyncio.gather(good_worker(\"A\"), good_worker(\"B\"))\n    # ~2 seconds total — both blocking calls genuinely overlap via separate threads\n\nasyncio.run(main())\n",[29,2691,2692,2698,2704,2708,2719,2739,2752,2772,2776,2786,2802,2807,2811],{"__ignoreMap":27},[32,2693,2694,2696],{"class":34,"line":35},[32,2695,59],{"class":58},[32,2697,63],{"class":62},[32,2699,2700,2702],{"class":34,"line":42},[32,2701,59],{"class":58},[32,2703,916],{"class":62},[32,2705,2706],{"class":34,"line":48},[32,2707,52],{"emptyLinePlaceholder":51},[32,2709,2710,2712,2714,2717],{"class":34,"line":55},[32,2711,96],{"class":58},[32,2713,99],{"class":58},[32,2715,2716],{"class":102}," good_worker",[32,2718,2563],{"class":62},[32,2720,2721,2723,2725,2727,2729,2731,2733,2735,2737],{"class":34,"line":66},[32,2722,720],{"class":115},[32,2724,196],{"class":62},[32,2726,290],{"class":58},[32,2728,305],{"class":177},[32,2730,296],{"class":115},[32,2732,1499],{"class":62},[32,2734,302],{"class":115},[32,2736,2582],{"class":177},[32,2738,308],{"class":62},[32,2740,2741,2743,2745,2747,2749],{"class":34,"line":74},[32,2742,981],{"class":58},[32,2744,1045],{"class":62},[32,2746,417],{"class":115},[32,2748,989],{"class":62},[32,2750,2751],{"class":38},"# runs the blocking call in a worker thread, event loop stays free\n",[32,2753,2754,2756,2758,2760,2762,2764,2766,2768,2770],{"class":34,"line":88},[32,2755,720],{"class":115},[32,2757,196],{"class":62},[32,2759,290],{"class":58},[32,2761,305],{"class":177},[32,2763,296],{"class":115},[32,2765,1499],{"class":62},[32,2767,302],{"class":115},[32,2769,2615],{"class":177},[32,2771,308],{"class":62},[32,2773,2774],{"class":34,"line":93},[32,2775,52],{"emptyLinePlaceholder":51},[32,2777,2778,2780,2782,2784],{"class":34,"line":109},[32,2779,96],{"class":58},[32,2781,99],{"class":58},[32,2783,774],{"class":102},[32,2785,777],{"class":62},[32,2787,2788,2790,2793,2795,2798,2800],{"class":34,"line":122},[32,2789,981],{"class":58},[32,2791,2792],{"class":62}," asyncio.gather(good_worker(",[32,2794,1543],{"class":177},[32,2796,2797],{"class":62},"), good_worker(",[32,2799,1558],{"class":177},[32,2801,1608],{"class":62},[32,2803,2804],{"class":34,"line":130},[32,2805,2806],{"class":38},"    # ~2 seconds total — both blocking calls genuinely overlap via separate threads\n",[32,2808,2809],{"class":34,"line":147},[32,2810,52],{"emptyLinePlaceholder":51},[32,2812,2813],{"class":34,"line":163},[32,2814,887],{"class":62},[2525,2816,2818],{"id":2817},"forgetting-to-await-a-coroutine","Forgetting to await a coroutine",[19,2820,2821],{"language":21},[23,2822,2824],{"className":25,"code":2823,"language":21,"meta":27,"style":27},"async def save_to_db(record):\n    await asyncio.sleep(0.1)\n    print(f\"saved {record}\")\n\nasync def main():\n    save_to_db(\"record-1\")    # BUG: missing `await` — schedules nothing, does nothing, warns at best\n    print(\"done\")\n\nasyncio.run(main())\n# done\n# \u003Csys>:0: RuntimeWarning: coroutine 'save_to_db' was never awaited\n",[29,2825,2826,2838,2848,2870,2874,2884,2903,2914,2918,2922,2927],{"__ignoreMap":27},[32,2827,2828,2830,2832,2835],{"class":34,"line":35},[32,2829,96],{"class":58},[32,2831,99],{"class":58},[32,2833,2834],{"class":102}," save_to_db",[32,2836,2837],{"class":62},"(record):\n",[32,2839,2840,2842,2844,2846],{"class":34,"line":42},[32,2841,981],{"class":58},[32,2843,984],{"class":62},[32,2845,1732],{"class":115},[32,2847,308],{"class":62},[32,2849,2850,2852,2854,2856,2859,2861,2864,2866,2868],{"class":34,"line":48},[32,2851,720],{"class":115},[32,2853,196],{"class":62},[32,2855,290],{"class":58},[32,2857,2858],{"class":177},"\"saved ",[32,2860,296],{"class":115},[32,2862,2863],{"class":62},"record",[32,2865,302],{"class":115},[32,2867,305],{"class":177},[32,2869,308],{"class":62},[32,2871,2872],{"class":34,"line":55},[32,2873,52],{"emptyLinePlaceholder":51},[32,2875,2876,2878,2880,2882],{"class":34,"line":66},[32,2877,96],{"class":58},[32,2879,99],{"class":58},[32,2881,774],{"class":102},[32,2883,777],{"class":62},[32,2885,2886,2889,2892,2894,2897,2900],{"class":34,"line":74},[32,2887,2888],{"class":62},"    save_to_db(",[32,2890,2891],{"class":177},"\"record-1\"",[32,2893,946],{"class":62},[32,2895,2896],{"class":38},"# ",[32,2898,2899],{"class":58},"BUG",[32,2901,2902],{"class":38},": missing `await` — schedules nothing, does nothing, warns at best\n",[32,2904,2905,2907,2909,2912],{"class":34,"line":88},[32,2906,720],{"class":115},[32,2908,196],{"class":62},[32,2910,2911],{"class":177},"\"done\"",[32,2913,308],{"class":62},[32,2915,2916],{"class":34,"line":93},[32,2917,52],{"emptyLinePlaceholder":51},[32,2919,2920],{"class":34,"line":109},[32,2921,887],{"class":62},[32,2923,2924],{"class":34,"line":122},[32,2925,2926],{"class":38},"# done\n",[32,2928,2929],{"class":34,"line":130},[32,2930,2931],{"class":38},"# \u003Csys>:0: RuntimeWarning: coroutine 'save_to_db' was never awaited\n",[2525,2933,2935,2936,2939],{"id":2934},"mixing-asynciorun-calls-or-calling-it-from-inside-already-running-async-code","Mixing ",[29,2937,2938],{},"asyncio.run()"," calls, or calling it from inside already-running async code",[19,2941,2942],{"language":21},[23,2943,2945],{"className":25,"code":2944,"language":21,"meta":27,"style":27},"async def inner():\n    return 1\n\nasync def outer():\n    # result = asyncio.run(inner())   # RuntimeError: asyncio.run() cannot be called from a running event loop\n    result = await inner()              # CORRECT — just await directly, you're already inside the loop\n    return result\n",[29,2946,2947,2958,2964,2968,2979,2984,2998],{"__ignoreMap":27},[32,2948,2949,2951,2953,2956],{"class":34,"line":35},[32,2950,96],{"class":58},[32,2952,99],{"class":58},[32,2954,2955],{"class":102}," inner",[32,2957,777],{"class":62},[32,2959,2960,2962],{"class":34,"line":42},[32,2961,756],{"class":58},[32,2963,2285],{"class":115},[32,2965,2966],{"class":34,"line":48},[32,2967,52],{"emptyLinePlaceholder":51},[32,2969,2970,2972,2974,2977],{"class":34,"line":55},[32,2971,96],{"class":58},[32,2973,99],{"class":58},[32,2975,2976],{"class":102}," outer",[32,2978,777],{"class":62},[32,2980,2981],{"class":34,"line":66},[32,2982,2983],{"class":38},"    # result = asyncio.run(inner())   # RuntimeError: asyncio.run() cannot be called from a running event loop\n",[32,2985,2986,2988,2990,2992,2995],{"class":34,"line":74},[32,2987,1038],{"class":62},[32,2989,406],{"class":58},[32,2991,565],{"class":58},[32,2993,2994],{"class":62}," inner()              ",[32,2996,2997],{"class":38},"# CORRECT — just await directly, you're already inside the loop\n",[32,2999,3000,3002],{"class":34,"line":88},[32,3001,756],{"class":58},[32,3003,1059],{"class":62},[1655,3005,3006,3008],{},[29,3007,2938],{}," is meant to be called exactly once, from synchronous top-level code, to start the event loop — never from inside a coroutine that's already running on one.",[14,3010,3012,3015],{"id":3011},"asynciowait_for-timeouts",[29,3013,3014],{},"asyncio.wait_for"," — Timeouts",[19,3017,3018],{"language":21},[23,3019,3021],{"className":25,"code":3020,"language":21,"meta":27,"style":27},"import asyncio\n\nasync def slow_operation():\n    await asyncio.sleep(5)\n    return \"done\"\n\nasync def main():\n    try:\n        result = await asyncio.wait_for(slow_operation(), timeout=1.0)\n    except asyncio.TimeoutError:\n        print(\"operation timed out\")    # raised after 1s, the underlying task is cancelled automatically\n\nasyncio.run(main())\n",[29,3022,3023,3029,3033,3044,3054,3060,3064,3074,3080,3102,3109,3123,3127],{"__ignoreMap":27},[32,3024,3025,3027],{"class":34,"line":35},[32,3026,59],{"class":58},[32,3028,63],{"class":62},[32,3030,3031],{"class":34,"line":42},[32,3032,52],{"emptyLinePlaceholder":51},[32,3034,3035,3037,3039,3042],{"class":34,"line":48},[32,3036,96],{"class":58},[32,3038,99],{"class":58},[32,3040,3041],{"class":102}," slow_operation",[32,3043,777],{"class":62},[32,3045,3046,3048,3050,3052],{"class":34,"line":55},[32,3047,981],{"class":58},[32,3049,984],{"class":62},[32,3051,1383],{"class":115},[32,3053,308],{"class":62},[32,3055,3056,3058],{"class":34,"line":66},[32,3057,756],{"class":58},[32,3059,956],{"class":177},[32,3061,3062],{"class":34,"line":74},[32,3063,52],{"emptyLinePlaceholder":51},[32,3065,3066,3068,3070,3072],{"class":34,"line":88},[32,3067,96],{"class":58},[32,3069,99],{"class":58},[32,3071,774],{"class":102},[32,3073,777],{"class":62},[32,3075,3076,3078],{"class":34,"line":93},[32,3077,1800],{"class":58},[32,3079,220],{"class":62},[32,3081,3082,3085,3087,3089,3092,3095,3097,3100],{"class":34,"line":109},[32,3083,3084],{"class":62},"        result ",[32,3086,406],{"class":58},[32,3088,565],{"class":58},[32,3090,3091],{"class":62}," asyncio.wait_for(slow_operation(), ",[32,3093,3094],{"class":618},"timeout",[32,3096,406],{"class":58},[32,3098,3099],{"class":115},"1.0",[32,3101,308],{"class":62},[32,3103,3104,3106],{"class":34,"line":122},[32,3105,1842],{"class":58},[32,3107,3108],{"class":62}," asyncio.TimeoutError:\n",[32,3110,3111,3113,3115,3118,3120],{"class":34,"line":130},[32,3112,1854],{"class":115},[32,3114,196],{"class":62},[32,3116,3117],{"class":177},"\"operation timed out\"",[32,3119,946],{"class":62},[32,3121,3122],{"class":38},"# raised after 1s, the underlying task is cancelled automatically\n",[32,3124,3125],{"class":34,"line":147},[32,3126,52],{"emptyLinePlaceholder":51},[32,3128,3129],{"class":34,"line":163},[32,3130,887],{"class":62},[14,3132,3134,3137],{"id":3133},"asynciolock-yes-async-code-can-still-have-race-conditions",[29,3135,3136],{},"asyncio.Lock"," — Yes, Async Code Can Still Have Race Conditions",[19,3139,3140],{"language":21},[23,3141,3143],{"className":25,"code":3142,"language":21,"meta":27,"style":27},"import asyncio\n\nbalance = 100\n\nasync def withdraw_unsafe(amount):\n    global balance\n    current = balance\n    await asyncio.sleep(0.01)   # simulates an await point where ANOTHER task can interleave\n    balance = current - amount\n\nasync def main():\n    await asyncio.gather(withdraw_unsafe(50), withdraw_unsafe(50))\n    print(balance)   # expected 0, but often prints 50 — both tasks read balance=100 before either wrote back\n\nasyncio.run(main())\n",[29,3144,3145,3151,3155,3165,3169,3181,3189,3198,3212,3227,3231,3241,3257,3267,3271],{"__ignoreMap":27},[32,3146,3147,3149],{"class":34,"line":35},[32,3148,59],{"class":58},[32,3150,63],{"class":62},[32,3152,3153],{"class":34,"line":42},[32,3154,52],{"emptyLinePlaceholder":51},[32,3156,3157,3160,3162],{"class":34,"line":48},[32,3158,3159],{"class":62},"balance ",[32,3161,406],{"class":58},[32,3163,3164],{"class":115}," 100\n",[32,3166,3167],{"class":34,"line":55},[32,3168,52],{"emptyLinePlaceholder":51},[32,3170,3171,3173,3175,3178],{"class":34,"line":66},[32,3172,96],{"class":58},[32,3174,99],{"class":58},[32,3176,3177],{"class":102}," withdraw_unsafe",[32,3179,3180],{"class":62},"(amount):\n",[32,3182,3183,3186],{"class":34,"line":74},[32,3184,3185],{"class":58},"    global",[32,3187,3188],{"class":62}," balance\n",[32,3190,3191,3194,3196],{"class":34,"line":88},[32,3192,3193],{"class":62},"    current ",[32,3195,406],{"class":58},[32,3197,3188],{"class":62},[32,3199,3200,3202,3204,3207,3209],{"class":34,"line":93},[32,3201,981],{"class":58},[32,3203,984],{"class":62},[32,3205,3206],{"class":115},"0.01",[32,3208,989],{"class":62},[32,3210,3211],{"class":38},"# simulates an await point where ANOTHER task can interleave\n",[32,3213,3214,3217,3219,3222,3224],{"class":34,"line":109},[32,3215,3216],{"class":62},"    balance ",[32,3218,406],{"class":58},[32,3220,3221],{"class":62}," current ",[32,3223,426],{"class":58},[32,3225,3226],{"class":62}," amount\n",[32,3228,3229],{"class":34,"line":122},[32,3230,52],{"emptyLinePlaceholder":51},[32,3232,3233,3235,3237,3239],{"class":34,"line":130},[32,3234,96],{"class":58},[32,3236,99],{"class":58},[32,3238,774],{"class":102},[32,3240,777],{"class":62},[32,3242,3243,3245,3248,3250,3253,3255],{"class":34,"line":147},[32,3244,981],{"class":58},[32,3246,3247],{"class":62}," asyncio.gather(withdraw_unsafe(",[32,3249,817],{"class":115},[32,3251,3252],{"class":62},"), withdraw_unsafe(",[32,3254,817],{"class":115},[32,3256,1608],{"class":62},[32,3258,3259,3261,3264],{"class":34,"line":163},[32,3260,720],{"class":115},[32,3262,3263],{"class":62},"(balance)   ",[32,3265,3266],{"class":38},"# expected 0, but often prints 50 — both tasks read balance=100 before either wrote back\n",[32,3268,3269],{"class":34,"line":174},[32,3270,52],{"emptyLinePlaceholder":51},[32,3272,3273],{"class":34,"line":181},[32,3274,887],{"class":62},[1655,3276,3277,3278,3280,3281,3283,3284,3286,3287,3290,3291,3293],{},"Even without OS-level thread preemption, an ",[29,3279,1671],{}," point is exactly where another task can run and mutate shared state — the same class of bug as chapter 21's threading race conditions, just triggered by explicit ",[29,3282,1671],{}," instead of an arbitrary GIL switch. ",[29,3285,3136],{}," fixes it identically to ",[29,3288,3289],{},"threading.Lock",", just with ",[29,3292,1967],{},":",[19,3295,3296],{"language":21},[23,3297,3299],{"className":25,"code":3298,"language":21,"meta":27,"style":27},"import asyncio\n\nbalance = 100\nlock = asyncio.Lock()\n\nasync def withdraw_safe(amount):\n    global balance\n    async with lock:\n        current = balance\n        await asyncio.sleep(0.01)\n        balance = current - amount\n\nasync def main():\n    await asyncio.gather(withdraw_safe(50), withdraw_safe(50))\n    print(balance)   # reliably 0\n\nasyncio.run(main())\n",[29,3300,3301,3307,3311,3319,3329,3333,3344,3350,3359,3368,3378,3391,3395,3405,3421,3430,3434],{"__ignoreMap":27},[32,3302,3303,3305],{"class":34,"line":35},[32,3304,59],{"class":58},[32,3306,63],{"class":62},[32,3308,3309],{"class":34,"line":42},[32,3310,52],{"emptyLinePlaceholder":51},[32,3312,3313,3315,3317],{"class":34,"line":48},[32,3314,3159],{"class":62},[32,3316,406],{"class":58},[32,3318,3164],{"class":115},[32,3320,3321,3324,3326],{"class":34,"line":55},[32,3322,3323],{"class":62},"lock ",[32,3325,406],{"class":58},[32,3327,3328],{"class":62}," asyncio.Lock()\n",[32,3330,3331],{"class":34,"line":66},[32,3332,52],{"emptyLinePlaceholder":51},[32,3334,3335,3337,3339,3342],{"class":34,"line":74},[32,3336,96],{"class":58},[32,3338,99],{"class":58},[32,3340,3341],{"class":102}," withdraw_safe",[32,3343,3180],{"class":62},[32,3345,3346,3348],{"class":34,"line":88},[32,3347,3185],{"class":58},[32,3349,3188],{"class":62},[32,3351,3352,3354,3356],{"class":34,"line":93},[32,3353,526],{"class":58},[32,3355,551],{"class":58},[32,3357,3358],{"class":62}," lock:\n",[32,3360,3361,3364,3366],{"class":34,"line":109},[32,3362,3363],{"class":62},"        current ",[32,3365,406],{"class":58},[32,3367,3188],{"class":62},[32,3369,3370,3372,3374,3376],{"class":34,"line":122},[32,3371,1342],{"class":58},[32,3373,984],{"class":62},[32,3375,3206],{"class":115},[32,3377,308],{"class":62},[32,3379,3380,3383,3385,3387,3389],{"class":34,"line":130},[32,3381,3382],{"class":62},"        balance ",[32,3384,406],{"class":58},[32,3386,3221],{"class":62},[32,3388,426],{"class":58},[32,3390,3226],{"class":62},[32,3392,3393],{"class":34,"line":147},[32,3394,52],{"emptyLinePlaceholder":51},[32,3396,3397,3399,3401,3403],{"class":34,"line":163},[32,3398,96],{"class":58},[32,3400,99],{"class":58},[32,3402,774],{"class":102},[32,3404,777],{"class":62},[32,3406,3407,3409,3412,3414,3417,3419],{"class":34,"line":174},[32,3408,981],{"class":58},[32,3410,3411],{"class":62}," asyncio.gather(withdraw_safe(",[32,3413,817],{"class":115},[32,3415,3416],{"class":62},"), withdraw_safe(",[32,3418,817],{"class":115},[32,3420,1608],{"class":62},[32,3422,3423,3425,3427],{"class":34,"line":181},[32,3424,720],{"class":115},[32,3426,3263],{"class":62},[32,3428,3429],{"class":38},"# reliably 0\n",[32,3431,3432],{"class":34,"line":214},[32,3433,52],{"emptyLinePlaceholder":51},[32,3435,3436],{"class":34,"line":223},[32,3437,887],{"class":62},[14,3439,3441],{"id":3440},"tips-tricks","💡 Tips & Tricks",[3443,3444,3445,3458,3471,3484,3493],"ul",{},[3446,3447,3448,3451,3452,3454,3455,3457],"li",{},[1659,3449,3450],{},"Idiom",": use ",[29,3453,1667],{}," the moment you want two or more independent coroutines to overlap — ",[29,3456,1671],{},"-ing coroutines back-to-back is sequential by default, which surprises developers coming from other async models where concurrency is implicit.",[3446,3459,3460,334,3463,3466,3467,3470],{},[1659,3461,3462],{},"Debug",[29,3464,3465],{},"asyncio.run(main(), debug=True)"," (or setting ",[29,3468,3469],{},"PYTHONASYNCIODEBUG=1",") enables slow-callback warnings and better tracebacks for \"coroutine never awaited\" and similar mistakes — invaluable while learning.",[3446,3472,3473,334,3476,3479,3480,3483],{},[1659,3474,3475],{},"Performance",[29,3477,3478],{},"asyncio.to_thread()"," (3.9+) is the simplest way to keep a genuinely blocking call (synchronous library, CPU-light blocking I\u002FO) from freezing the entire event loop, without hand-rolling a ",[29,3481,3482],{},"ThreadPoolExecutor",".",[3446,3485,3486,3488,3489,3492],{},[1659,3487,3450],{},": name every task you create (",[29,3490,3491],{},"asyncio.create_task(coro(), name=\"fetch-user\")",") — an unhandled exception in an un-awaited, unnamed task is much harder to trace back to its origin in logs than a named one.",[3446,3494,3495,3498,3499,3502],{},[1659,3496,3497],{},"Safety",": an ",[29,3500,3501],{},"asyncio.Task"," that's created but never awaited or stored can be garbage-collected before it finishes, silently cancelling it — keep a reference (e.g., in a set) to any \"fire and forget\" background task until it completes.",[14,3504,3506],{"id":3505},"️-edge-cases-gotchas","⚠️ Edge Cases & Gotchas",[3443,3508,3509,3525,3539,3551,3564],{},[3446,3510,3511,3517,3518,3520,3521,3524],{},[1659,3512,3513,3514,3516],{},"Calling an ",[29,3515,2504],{}," function does not run it — it returns a coroutine object that does nothing until awaited or scheduled",", and forgetting the ",[29,3519,1671],{}," produces no error, only a ",[29,3522,3523],{},"RuntimeWarning"," that's easy to miss in noisy logs.",[3446,3526,3527,3534,3535,3538],{},[1659,3528,3529,3530,3533],{},"A single blocking call (",[29,3531,3532],{},"time.sleep",", a synchronous HTTP request, blocking disk I\u002FO) anywhere in a coroutine freezes the entire event loop",", stalling every other coroutine scheduled on it — not just the offending one — because ",[29,3536,3537],{},"asyncio"," concurrency is entirely cooperative on one thread.",[3446,3540,3541,3550],{},[1659,3542,3543,3546,3547,3549],{},[29,3544,3545],{},"asyncio.gather()"," without ",[29,3548,1948],{}," raises only the first exception it encounters, while the other coroutines keep running in the background, unawaited and unmonitored"," — their eventual results or further exceptions are silently discarded unless explicitly handled.",[3446,3552,3553,3558,3559,3561,3562,3483],{},[1659,3554,3555,3557],{},[29,3556,1671],{}," points are exactly where other tasks can interleave and mutate shared state"," — async code is not automatically free of race conditions just because there's only one thread; any shared mutable state read before an ",[29,3560,1671],{}," and written after it needs an ",[29,3563,3136],{},[3446,3565,3566,3571,3572,3575],{},[1659,3567,2501,3568,3570],{},[29,3569,3501],{}," with no remaining strong reference can be garbage-collected mid-flight, silently cancelling it"," — a \"fire and forget\" ",[29,3573,3574],{},"asyncio.create_task(...)"," call with the return value discarded is a common source of work that mysteriously never completes.",[14,3577,3579],{"id":3578},"spot-the-bug","🧠 Spot the Bug",[1655,3581,3582],{},"A function is meant to fetch three resources concurrently and return all results. It works, but takes about three times longer than expected. Find the bug.",[19,3584,3585],{"language":21},[23,3586,3588],{"className":25,"code":3587,"language":21,"meta":27,"style":27},"import asyncio\n\nasync def fetch(name):\n    await asyncio.sleep(1)\n    return f\"{name}-data\"\n\nasync def fetch_all():\n    results = []\n    for name in [\"a\", \"b\", \"c\"]:\n        result = await fetch(name)\n        results.append(result)\n    return results\n\nasyncio.run(fetch_all())   # takes ~3 seconds instead of ~1\n",[29,3589,3590,3596,3600,3610,3620,3637,3641,3652,3661,3688,3699,3704,3711,3715],{"__ignoreMap":27},[32,3591,3592,3594],{"class":34,"line":35},[32,3593,59],{"class":58},[32,3595,63],{"class":62},[32,3597,3598],{"class":34,"line":42},[32,3599,52],{"emptyLinePlaceholder":51},[32,3601,3602,3604,3606,3608],{"class":34,"line":48},[32,3603,96],{"class":58},[32,3605,99],{"class":58},[32,3607,1713],{"class":102},[32,3609,2563],{"class":62},[32,3611,3612,3614,3616,3618],{"class":34,"line":55},[32,3613,981],{"class":58},[32,3615,984],{"class":62},[32,3617,199],{"class":115},[32,3619,308],{"class":62},[32,3621,3622,3624,3626,3628,3630,3632,3634],{"class":34,"line":66},[32,3623,756],{"class":58},[32,3625,2400],{"class":58},[32,3627,305],{"class":177},[32,3629,296],{"class":115},[32,3631,1499],{"class":62},[32,3633,302],{"class":115},[32,3635,3636],{"class":177},"-data\"\n",[32,3638,3639],{"class":34,"line":74},[32,3640,52],{"emptyLinePlaceholder":51},[32,3642,3643,3645,3647,3650],{"class":34,"line":88},[32,3644,96],{"class":58},[32,3646,99],{"class":58},[32,3648,3649],{"class":102}," fetch_all",[32,3651,777],{"class":62},[32,3653,3654,3656,3658],{"class":34,"line":93},[32,3655,585],{"class":62},[32,3657,406],{"class":58},[32,3659,3660],{"class":62}," []\n",[32,3662,3663,3665,3668,3670,3672,3675,3677,3680,3682,3685],{"class":34,"line":109},[32,3664,184],{"class":58},[32,3666,3667],{"class":62}," name ",[32,3669,190],{"class":58},[32,3671,788],{"class":62},[32,3673,3674],{"class":177},"\"a\"",[32,3676,241],{"class":62},[32,3678,3679],{"class":177},"\"b\"",[32,3681,241],{"class":62},[32,3683,3684],{"class":177},"\"c\"",[32,3686,3687],{"class":62},"]:\n",[32,3689,3690,3692,3694,3696],{"class":34,"line":122},[32,3691,3084],{"class":62},[32,3693,406],{"class":58},[32,3695,565],{"class":58},[32,3697,3698],{"class":62}," fetch(name)\n",[32,3700,3701],{"class":34,"line":130},[32,3702,3703],{"class":62},"        results.append(result)\n",[32,3705,3706,3708],{"class":34,"line":147},[32,3707,756],{"class":58},[32,3709,3710],{"class":62}," results\n",[32,3712,3713],{"class":34,"line":163},[32,3714,52],{"emptyLinePlaceholder":51},[32,3716,3717,3720],{"class":34,"line":174},[32,3718,3719],{"class":62},"asyncio.run(fetch_all())   ",[32,3721,3722],{"class":38},"# takes ~3 seconds instead of ~1\n",[3724,3725,3726,3730,3762,3765,3817,3824],"details",{},[3727,3728,3729],"summary",{},"Answer",[1655,3731,3732,3735,3736,3738,3739,3742,3743,3745,3746,3748,3749,3752,3753,3755,3756,3758,3759,3761],{},[29,3733,3734],{},"await fetch(name)"," inside the ",[29,3737,604],{}," loop runs each call to completion before starting the next one — this is sequential execution, not concurrent, despite ",[29,3740,3741],{},"fetch"," being an ",[29,3744,2504],{}," function. ",[29,3747,2504],{}," alone does not make calls run in parallel; it only makes a function ",[1954,3750,3751],{},"awaitable"," and able to suspend at its own ",[29,3754,1671],{}," points. Concurrency requires actually scheduling multiple coroutines to run before waiting on any of them, either via ",[29,3757,1667],{}," for each, or by passing all of them to ",[29,3760,3545],{}," at once — awaiting one at a time in a loop is functionally identical to synchronous code, just with extra syntax.",[1655,3763,3764],{},"The fix schedules all three fetches before awaiting any of them:",[19,3766,3767],{"language":21},[23,3768,3770],{"className":25,"code":3769,"language":21,"meta":27,"style":27},"async def fetch_all():\n    return await asyncio.gather(*(fetch(name) for name in [\"a\", \"b\", \"c\"]))\n",[29,3771,3772,3782],{"__ignoreMap":27},[32,3773,3774,3776,3778,3780],{"class":34,"line":35},[32,3775,96],{"class":58},[32,3777,99],{"class":58},[32,3779,3649],{"class":102},[32,3781,777],{"class":62},[32,3783,3784,3786,3788,3791,3793,3796,3798,3800,3802,3804,3806,3808,3810,3812,3814],{"class":34,"line":42},[32,3785,756],{"class":58},[32,3787,565],{"class":58},[32,3789,3790],{"class":62}," asyncio.gather(",[32,3792,412],{"class":58},[32,3794,3795],{"class":62},"(fetch(name) ",[32,3797,604],{"class":58},[32,3799,3667],{"class":62},[32,3801,190],{"class":58},[32,3803,788],{"class":62},[32,3805,3674],{"class":177},[32,3807,241],{"class":62},[32,3809,3679],{"class":177},[32,3811,241],{"class":62},[32,3813,3684],{"class":177},[32,3815,3816],{"class":62},"]))\n",[1655,3818,3819,3820,3823],{},"This runs all three ",[29,3821,3822],{},"asyncio.sleep(1)"," calls concurrently, so the whole function takes about 1 second instead of 3, since all three coroutines are suspended waiting on their sleeps at the same time rather than one after another.",[1655,3825,3826,334,3829,2130,3831,3833,3834,241,3836,3838],{},[1659,3827,3828],{},"The lesson",[29,3830,96],{},[29,3832,1671],{}," syntax alone doesn't imply concurrency — awaiting coroutines one at a time in sequence is sequential; genuine concurrency requires ",[29,3835,3545],{},[29,3837,1667],{},", or equivalent, to have more than one coroutine actually in flight simultaneously.",[14,3840,3842],{"id":3841},"key-takeaways","Key Takeaways",[3443,3844,3845,3853,3860,3869,3875,3885],{},[3446,3846,3847,3849,3850,3852],{},[29,3848,3537],{}," provides single-threaded, cooperative concurrency — control only switches at explicit ",[29,3851,1671],{}," points, which avoids OS-thread overhead and preemption-based race conditions, but means one blocking call can freeze everything.",[3446,3854,3513,3855,3857,3858,3483],{},[29,3856,2504],{}," function returns a coroutine object immediately without running its body; the body only executes once the coroutine is awaited or scheduled as a ",[29,3859,1663],{},[3446,3861,3862,3863,3865,3866,3868],{},"Awaiting coroutines one after another in a loop or in sequence is sequential execution — real concurrency requires ",[29,3864,3545],{}," or ",[29,3867,1667],{}," to have multiple coroutines in flight at once.",[3446,3870,3871,3872,3874],{},"Any blocking (non-async) call inside a coroutine stalls the entire event loop; use async-native libraries or ",[29,3873,3478],{}," to offload genuinely blocking work.",[3446,3876,3877,3546,3879,3881,3882,3884],{},[29,3878,3545],{},[29,3880,1948],{}," raises only the first exception, leaving other coroutines running in the background with their results discarded — use ",[29,3883,1948],{}," to collect everything.",[3446,3886,3887,3888,3890,3891,3893,3894,3896],{},"Shared mutable state is still subject to race conditions in async code, because other tasks can run during any ",[29,3889,1671],{}," — protect read-modify-write sequences with ",[29,3892,3136],{}," exactly as ",[29,3895,3289],{}," protects them in threaded code.",[3898,3899,3900],"style",{},"html pre.shiki code .sdCPZ, html code.shiki .sdCPZ{--shiki-default:#6A737D;--shiki-github-dark:#6A737D}html pre.shiki code .svdQ7, html code.shiki .svdQ7{--shiki-default:#D73A49;--shiki-github-dark:#F97583}html pre.shiki code .ssxIu, html code.shiki .ssxIu{--shiki-default:#24292E;--shiki-github-dark:#E1E4E8}html pre.shiki code .sIsaT, html code.shiki .sIsaT{--shiki-default:#6F42C1;--shiki-github-dark:#B392F0}html pre.shiki code .snvgF, html code.shiki .snvgF{--shiki-default:#005CC5;--shiki-github-dark:#79B8FF}html pre.shiki code .sJ6F3, html code.shiki .sJ6F3{--shiki-default:#032F62;--shiki-github-dark:#9ECBFF}html pre.shiki code .sCrzJ, html code.shiki .sCrzJ{--shiki-default:#E36209;--shiki-github-dark:#FFAB70}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);}html .github-dark .shiki span {color: var(--shiki-github-dark);background: var(--shiki-github-dark-bg);font-style: var(--shiki-github-dark-font-style);font-weight: var(--shiki-github-dark-font-weight);text-decoration: var(--shiki-github-dark-text-decoration);}html.github-dark .shiki span {color: var(--shiki-github-dark);background: var(--shiki-github-dark-bg);font-style: var(--shiki-github-dark-font-style);font-weight: var(--shiki-github-dark-font-weight);text-decoration: var(--shiki-github-dark-text-decoration);}",{"title":27,"searchDepth":42,"depth":42,"links":3902},[3903,3904,3905,3907,3909,3910,3916,3918,3920,3921,3922,3923],{"id":16,"depth":42,"text":17},{"id":1448,"depth":42,"text":1449},{"id":1683,"depth":42,"text":3906},"asyncio.gather — Running Many Coroutines Concurrently",{"id":1964,"depth":42,"text":3908},"async with and async for",{"id":2345,"depth":42,"text":2346},{"id":2522,"depth":42,"text":2523,"children":3911},[3912,3913,3914],{"id":2527,"depth":48,"text":2528},{"id":2817,"depth":48,"text":2818},{"id":2934,"depth":48,"text":3915},"Mixing asyncio.run() calls, or calling it from inside already-running async code",{"id":3011,"depth":42,"text":3917},"asyncio.wait_for — Timeouts",{"id":3133,"depth":42,"text":3919},"asyncio.Lock — Yes, Async Code Can Still Have Race Conditions",{"id":3440,"depth":42,"text":3441},{"id":3505,"depth":42,"text":3506},{"id":3578,"depth":42,"text":3579},{"id":3841,"depth":42,"text":3842},"md",{},"\u002Fpython\u002F22-async-await",{"title":5,"description":27},"python\u002F22-async-await","larRCMDzVT50-j5Qn6t9zSg-dhC0YMKetMqthwr_a9w",1789924651696]