[{"data":1,"prerenderedAt":836},["ShallowReactive",2],{"blog-\u002Fblog\u002Fpython-asyncio-async-await-explained":3},{"id":4,"title":5,"body":6,"description":822,"difficulty":823,"extension":824,"framework":825,"frameworkSlug":55,"meta":826,"navigation":125,"order":113,"path":827,"qaPath":828,"seo":829,"stem":830,"subtopic":831,"topic":832,"topicSlug":833,"updated":834,"__hash__":835},"blog\u002Fblog\u002Fpython-asyncio-async-await-explained.md","Python asyncio Explained — Coroutines, the Event Loop, await, and Running Tasks Concurrently",{"type":7,"value":8,"toc":811},"minimark",[9,14,31,35,50,161,169,173,182,325,340,344,357,367,371,382,530,537,541,555,626,635,639,642,744,758,762,773,777,807],[10,11,13],"h2",{"id":12},"python-asyncio-explained","Python asyncio, explained",[15,16,17,21,22,26,27,30],"p",{},[18,19,20],"code",{},"asyncio"," is Python's answer to ",[23,24,25],"strong",{},"I\u002FO-bound concurrency"," — handling thousands of network\nconnections, file reads, or API calls without spawning a thread per task. It trips people\nup because it introduces three new ideas at once: coroutines, ",[18,28,29],{},"await",", and an event loop.\nThis guide separates them so you can reason about what actually runs when.",[10,32,34],{"id":33},"what-a-coroutine-actually-is","What a coroutine actually is",[15,36,37,38,41,42,45,46,49],{},"Defining a function with ",[18,39,40],{},"async def"," doesn't run anything — it creates a ",[23,43,44],{},"coroutine\nfunction",". Calling it returns a ",[23,47,48],{},"coroutine object",", a paused computation that does\nnothing until it's driven by an event loop.",[51,52,57],"pre",{"className":53,"code":54,"language":55,"meta":56,"style":56},"language-python shiki shiki-themes github-light github-dark","async def fetch(name):\n    print(f\"start {name}\")\n    return name.upper()\n\ncoro = fetch(\"a\")        # nothing printed yet — just a coroutine object\nprint(coro)              # \u003Ccoroutine object fetch at 0x...>\n","python","",[18,58,59,79,111,120,127,149],{"__ignoreMap":56},[60,61,64,68,71,75],"span",{"class":62,"line":63},"line",1,[60,65,67],{"class":66},"szBVR","async",[60,69,70],{"class":66}," def",[60,72,74],{"class":73},"sScJk"," fetch",[60,76,78],{"class":77},"sVt8B","(name):\n",[60,80,82,86,89,92,96,99,102,105,108],{"class":62,"line":81},2,[60,83,85],{"class":84},"sj4cs","    print",[60,87,88],{"class":77},"(",[60,90,91],{"class":66},"f",[60,93,95],{"class":94},"sZZnC","\"start ",[60,97,98],{"class":84},"{",[60,100,101],{"class":77},"name",[60,103,104],{"class":84},"}",[60,106,107],{"class":94},"\"",[60,109,110],{"class":77},")\n",[60,112,114,117],{"class":62,"line":113},3,[60,115,116],{"class":66},"    return",[60,118,119],{"class":77}," name.upper()\n",[60,121,123],{"class":62,"line":122},4,[60,124,126],{"emptyLinePlaceholder":125},true,"\n",[60,128,130,133,136,139,142,145],{"class":62,"line":129},5,[60,131,132],{"class":77},"coro ",[60,134,135],{"class":66},"=",[60,137,138],{"class":77}," fetch(",[60,140,141],{"class":94},"\"a\"",[60,143,144],{"class":77},")        ",[60,146,148],{"class":147},"sJ8bj","# nothing printed yet — just a coroutine object\n",[60,150,152,155,158],{"class":62,"line":151},6,[60,153,154],{"class":84},"print",[60,156,157],{"class":77},"(coro)              ",[60,159,160],{"class":147},"# \u003Ccoroutine object fetch at 0x...>\n",[15,162,163,164,168],{},"That last line even warns you the coroutine was never awaited. A coroutine is inert; it\nneeds something to ",[165,166,167],"em",{},"drive"," it.",[10,170,172],{"id":171},"await-suspends-it-doesnt-block","await suspends, it doesn't block",[15,174,175,177,178,181],{},[18,176,29],{}," does two things: it runs another awaitable to completion, and — crucially — it\n",[23,179,180],{},"yields control back to the event loop"," while waiting. That's the whole point. While one\ncoroutine is parked on a slow network read, the loop runs others.",[51,183,185],{"className":53,"code":184,"language":55,"meta":56,"style":56},"import asyncio\n\nasync def task(name, delay):\n    await asyncio.sleep(delay)      # yields to the loop; doesn't block the thread\n    print(f\"{name} done after {delay}s\")\n    return name\n\nasync def main():\n    result = await task(\"a\", 1)     # runs to completion before continuing\n    print(result)\n\nasyncio.run(main())\n",[18,186,187,195,199,211,222,253,260,265,278,306,314,319],{"__ignoreMap":56},[60,188,189,192],{"class":62,"line":63},[60,190,191],{"class":66},"import",[60,193,194],{"class":77}," asyncio\n",[60,196,197],{"class":62,"line":81},[60,198,126],{"emptyLinePlaceholder":125},[60,200,201,203,205,208],{"class":62,"line":113},[60,202,67],{"class":66},[60,204,70],{"class":66},[60,206,207],{"class":73}," task",[60,209,210],{"class":77},"(name, delay):\n",[60,212,213,216,219],{"class":62,"line":122},[60,214,215],{"class":66},"    await",[60,217,218],{"class":77}," asyncio.sleep(delay)      ",[60,220,221],{"class":147},"# yields to the loop; doesn't block the thread\n",[60,223,224,226,228,230,232,234,236,238,241,243,246,248,251],{"class":62,"line":129},[60,225,85],{"class":84},[60,227,88],{"class":77},[60,229,91],{"class":66},[60,231,107],{"class":94},[60,233,98],{"class":84},[60,235,101],{"class":77},[60,237,104],{"class":84},[60,239,240],{"class":94}," done after ",[60,242,98],{"class":84},[60,244,245],{"class":77},"delay",[60,247,104],{"class":84},[60,249,250],{"class":94},"s\"",[60,252,110],{"class":77},[60,254,255,257],{"class":62,"line":151},[60,256,116],{"class":66},[60,258,259],{"class":77}," name\n",[60,261,263],{"class":62,"line":262},7,[60,264,126],{"emptyLinePlaceholder":125},[60,266,268,270,272,275],{"class":62,"line":267},8,[60,269,67],{"class":66},[60,271,70],{"class":66},[60,273,274],{"class":73}," main",[60,276,277],{"class":77},"():\n",[60,279,281,284,286,289,292,294,297,300,303],{"class":62,"line":280},9,[60,282,283],{"class":77},"    result ",[60,285,135],{"class":66},[60,287,288],{"class":66}," await",[60,290,291],{"class":77}," task(",[60,293,141],{"class":94},[60,295,296],{"class":77},", ",[60,298,299],{"class":84},"1",[60,301,302],{"class":77},")     ",[60,304,305],{"class":147},"# runs to completion before continuing\n",[60,307,309,311],{"class":62,"line":308},10,[60,310,85],{"class":84},[60,312,313],{"class":77},"(result)\n",[60,315,317],{"class":62,"line":316},11,[60,318,126],{"emptyLinePlaceholder":125},[60,320,322],{"class":62,"line":321},12,[60,323,324],{"class":77},"asyncio.run(main())\n",[15,326,327,328,331,332,335,336,339],{},"Note ",[18,329,330],{},"await asyncio.sleep(1)"," is non-blocking, whereas ",[18,333,334],{},"time.sleep(1)"," would freeze the\n",[23,337,338],{},"entire"," loop. Mixing blocking calls into async code is the #1 asyncio mistake.",[10,341,343],{"id":342},"the-event-loop-is-a-single-thread","The event loop is a single thread",[15,345,346,347,349,350,353,354,356],{},"There is one thread and one event loop. It keeps a queue of ready tasks and runs them one\nat a time; each task runs until it hits an ",[18,348,29],{}," that can't complete immediately, then\nthe loop switches to another ready task. This is ",[23,351,352],{},"cooperative"," multitasking — tasks must\nyield via ",[18,355,29],{}," for anything else to run.",[15,358,359,360,363,364,366],{},"Because it's single-threaded, you don't need locks for ordinary in-memory state, and there\nare no race conditions ",[165,361,362],{},"between"," ",[18,365,29],{}," points. But CPU-heavy code still blocks everything.",[10,368,370],{"id":369},"running-things-concurrently-with-tasks","Running things concurrently with tasks",[15,372,373,374,377,378,381],{},"Awaiting coroutines one after another is sequential. To overlap them, wrap each in a\n",[23,375,376],{},"task"," (scheduled on the loop) and await them together with ",[18,379,380],{},"asyncio.gather",".",[51,383,385],{"className":53,"code":384,"language":55,"meta":56,"style":56},"import asyncio, time\n\nasync def work(n):\n    await asyncio.sleep(1)\n    return n * 2\n\nasync def main():\n    start = time.perf_counter()\n    results = await asyncio.gather(work(1), work(2), work(3))\n    print(results, f\"in {time.perf_counter() - start:.1f}s\")   # [2,4,6] in ~1.0s\n\nasyncio.run(main())\n",[18,386,387,394,398,410,421,434,438,448,458,486,522,526],{"__ignoreMap":56},[60,388,389,391],{"class":62,"line":63},[60,390,191],{"class":66},[60,392,393],{"class":77}," asyncio, time\n",[60,395,396],{"class":62,"line":81},[60,397,126],{"emptyLinePlaceholder":125},[60,399,400,402,404,407],{"class":62,"line":113},[60,401,67],{"class":66},[60,403,70],{"class":66},[60,405,406],{"class":73}," work",[60,408,409],{"class":77},"(n):\n",[60,411,412,414,417,419],{"class":62,"line":122},[60,413,215],{"class":66},[60,415,416],{"class":77}," asyncio.sleep(",[60,418,299],{"class":84},[60,420,110],{"class":77},[60,422,423,425,428,431],{"class":62,"line":129},[60,424,116],{"class":66},[60,426,427],{"class":77}," n ",[60,429,430],{"class":66},"*",[60,432,433],{"class":84}," 2\n",[60,435,436],{"class":62,"line":151},[60,437,126],{"emptyLinePlaceholder":125},[60,439,440,442,444,446],{"class":62,"line":262},[60,441,67],{"class":66},[60,443,70],{"class":66},[60,445,274],{"class":73},[60,447,277],{"class":77},[60,449,450,453,455],{"class":62,"line":267},[60,451,452],{"class":77},"    start ",[60,454,135],{"class":66},[60,456,457],{"class":77}," time.perf_counter()\n",[60,459,460,463,465,467,470,472,475,478,480,483],{"class":62,"line":280},[60,461,462],{"class":77},"    results ",[60,464,135],{"class":66},[60,466,288],{"class":66},[60,468,469],{"class":77}," asyncio.gather(work(",[60,471,299],{"class":84},[60,473,474],{"class":77},"), work(",[60,476,477],{"class":84},"2",[60,479,474],{"class":77},[60,481,482],{"class":84},"3",[60,484,485],{"class":77},"))\n",[60,487,488,490,493,495,498,500,503,506,509,512,514,516,519],{"class":62,"line":308},[60,489,85],{"class":84},[60,491,492],{"class":77},"(results, ",[60,494,91],{"class":66},[60,496,497],{"class":94},"\"in ",[60,499,98],{"class":84},[60,501,502],{"class":77},"time.perf_counter() ",[60,504,505],{"class":66},"-",[60,507,508],{"class":77}," start",[60,510,511],{"class":66},":.1f",[60,513,104],{"class":84},[60,515,250],{"class":94},[60,517,518],{"class":77},")   ",[60,520,521],{"class":147},"# [2,4,6] in ~1.0s\n",[60,523,524],{"class":62,"line":316},[60,525,126],{"emptyLinePlaceholder":125},[60,527,528],{"class":62,"line":321},[60,529,324],{"class":77},[15,531,532,533,536],{},"Three one-second sleeps finish in ~1 second, not 3, because they overlap. ",[18,534,535],{},"gather"," returns\nresults in argument order regardless of which finished first.",[10,538,540],{"id":539},"tasks-vs-coroutines","Tasks vs coroutines",[15,542,543,546,547,550,551,554],{},[18,544,545],{},"asyncio.create_task(coro)"," schedules a coroutine to run ",[23,548,549],{},"right away"," in the background\nand returns a ",[18,552,553],{},"Task"," handle. A bare coroutine only runs when awaited. Use tasks when you\nwant work to start before you await its result.",[51,556,558],{"className":53,"code":557,"language":55,"meta":56,"style":56},"async def main():\n    t = asyncio.create_task(work(10))   # starts running now\n    await asyncio.sleep(0.5)            # do other things meanwhile\n    print(await t)                      # collect the result later\n\nasyncio.run(main())\n",[18,559,560,570,589,604,618,622],{"__ignoreMap":56},[60,561,562,564,566,568],{"class":62,"line":63},[60,563,67],{"class":66},[60,565,70],{"class":66},[60,567,274],{"class":73},[60,569,277],{"class":77},[60,571,572,575,577,580,583,586],{"class":62,"line":81},[60,573,574],{"class":77},"    t ",[60,576,135],{"class":66},[60,578,579],{"class":77}," asyncio.create_task(work(",[60,581,582],{"class":84},"10",[60,584,585],{"class":77},"))   ",[60,587,588],{"class":147},"# starts running now\n",[60,590,591,593,595,598,601],{"class":62,"line":113},[60,592,215],{"class":66},[60,594,416],{"class":77},[60,596,597],{"class":84},"0.5",[60,599,600],{"class":77},")            ",[60,602,603],{"class":147},"# do other things meanwhile\n",[60,605,606,608,610,612,615],{"class":62,"line":122},[60,607,85],{"class":84},[60,609,88],{"class":77},[60,611,29],{"class":66},[60,613,614],{"class":77}," t)                      ",[60,616,617],{"class":147},"# collect the result later\n",[60,619,620],{"class":62,"line":129},[60,621,126],{"emptyLinePlaceholder":125},[60,623,624],{"class":62,"line":151},[60,625,324],{"class":77},[15,627,628,629,632,633,381],{},"Modern code often prefers ",[18,630,631],{},"asyncio.TaskGroup"," (3.11+), which awaits a set of tasks and\ncancels the rest if one fails — safer structured concurrency than raw ",[18,634,535],{},[10,636,638],{"id":637},"running-blocking-code-without-freezing-the-loop","Running blocking code without freezing the loop",[15,640,641],{},"When you must call a blocking function (a sync DB driver, heavy CPU work), offload it to a\nthread or process pool so the loop stays responsive.",[51,643,645],{"className":53,"code":644,"language":55,"meta":56,"style":56},"import asyncio\n\ndef blocking_io():\n    with open(\"big.log\") as f:\n        return len(f.read())\n\nasync def main():\n    size = await asyncio.to_thread(blocking_io)   # runs in a worker thread\n    print(size)\n\nasyncio.run(main())\n",[18,646,647,653,657,667,689,700,704,714,729,736,740],{"__ignoreMap":56},[60,648,649,651],{"class":62,"line":63},[60,650,191],{"class":66},[60,652,194],{"class":77},[60,654,655],{"class":62,"line":81},[60,656,126],{"emptyLinePlaceholder":125},[60,658,659,662,665],{"class":62,"line":113},[60,660,661],{"class":66},"def",[60,663,664],{"class":73}," blocking_io",[60,666,277],{"class":77},[60,668,669,672,675,677,680,683,686],{"class":62,"line":122},[60,670,671],{"class":66},"    with",[60,673,674],{"class":84}," open",[60,676,88],{"class":77},[60,678,679],{"class":94},"\"big.log\"",[60,681,682],{"class":77},") ",[60,684,685],{"class":66},"as",[60,687,688],{"class":77}," f:\n",[60,690,691,694,697],{"class":62,"line":129},[60,692,693],{"class":66},"        return",[60,695,696],{"class":84}," len",[60,698,699],{"class":77},"(f.read())\n",[60,701,702],{"class":62,"line":151},[60,703,126],{"emptyLinePlaceholder":125},[60,705,706,708,710,712],{"class":62,"line":262},[60,707,67],{"class":66},[60,709,70],{"class":66},[60,711,274],{"class":73},[60,713,277],{"class":77},[60,715,716,719,721,723,726],{"class":62,"line":267},[60,717,718],{"class":77},"    size ",[60,720,135],{"class":66},[60,722,288],{"class":66},[60,724,725],{"class":77}," asyncio.to_thread(blocking_io)   ",[60,727,728],{"class":147},"# runs in a worker thread\n",[60,730,731,733],{"class":62,"line":280},[60,732,85],{"class":84},[60,734,735],{"class":77},"(size)\n",[60,737,738],{"class":62,"line":308},[60,739,126],{"emptyLinePlaceholder":125},[60,741,742],{"class":62,"line":316},[60,743,324],{"class":77},[15,745,746,749,750,753,754,757],{},[18,747,748],{},"asyncio.to_thread"," is the clean way to bridge sync and async. For CPU-bound work, use\n",[18,751,752],{},"loop.run_in_executor"," with a ",[18,755,756],{},"ProcessPoolExecutor"," to escape the GIL.",[10,759,761],{"id":760},"where-asyncio-fits","Where asyncio fits",[15,763,764,765,768,769,772],{},"asyncio shines for ",[23,766,767],{},"I\u002FO-bound, high-concurrency"," workloads — web servers, scrapers,\nchat backends, anything waiting on many sockets at once. It is ",[165,770,771],{},"not"," a speedup for\nCPU-bound work (use multiprocessing) and adds real complexity, so don't reach for it when a\nhandful of threads or a simple sequential script would do.",[10,774,776],{"id":775},"recap","Recap",[15,778,779,781,782,785,786,789,790,792,793,796,797,799,800,803,804,806],{},[18,780,40],{}," creates ",[23,783,784],{},"coroutines"," — inert until driven by the ",[23,787,788],{},"event loop",", which runs\non a single thread. ",[18,791,29],{}," runs an awaitable and yields control while waiting, letting\nother tasks run cooperatively. To overlap work, schedule coroutines as ",[23,794,795],{},"tasks"," (or use\n",[18,798,535],{},"\u002F",[18,801,802],{},"TaskGroup","); awaiting them one by one stays sequential. Never call blocking\nfunctions directly inside async code — use ",[18,805,748],{}," or an executor. asyncio is\nthe right tool for massive I\u002FO concurrency, and the wrong one for CPU-bound speed.",[808,809,810],"style",{},"html pre.shiki code .szBVR, html code.shiki .szBVR{--shiki-default:#D73A49;--shiki-dark:#F97583}html pre.shiki code .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}html pre.shiki code .sJ8bj, html code.shiki .sJ8bj{--shiki-default:#6A737D;--shiki-dark:#6A737D}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 .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);}",{"title":56,"searchDepth":81,"depth":81,"links":812},[813,814,815,816,817,818,819,820,821],{"id":12,"depth":81,"text":13},{"id":33,"depth":81,"text":34},{"id":171,"depth":81,"text":172},{"id":342,"depth":81,"text":343},{"id":369,"depth":81,"text":370},{"id":539,"depth":81,"text":540},{"id":637,"depth":81,"text":638},{"id":760,"depth":81,"text":761},{"id":775,"depth":81,"text":776},"How Python's asyncio works — what a coroutine really is, what await does, how the event loop schedules tasks, and how to run many I\u002FO-bound operations concurrently without threads.","hard","md","Python",{},"\u002Fblog\u002Fpython-asyncio-async-await-explained","\u002Fpython\u002Fconcurrency\u002Fasyncio",{"title":5,"description":822},"blog\u002Fpython-asyncio-async-await-explained","asyncio & async\u002Fawait","Concurrency & Parallelism","concurrency","2026-06-19","nKh7TxntEAJpu0lSNS9PaySnXvDjQHXbHTsubUjpa00",1782244093339]