Real concurrency with asyncio.gather()
async/await alone doesn't give you concurrency
This is worth demonstrating directly, since it’s a natural assumption to
make: simply rewriting the three API calls
from Concept 1
as coroutines, and await-ing each one, does not make them run
concurrently:
Still 3.5 seconds — every
await still fully waits for that specific coroutine to finish before moving to the next line,
exactly like a regular function call would. async/await alone just
gives you the ability to pause and resume — it doesn’t automatically
run multiple things at once. Something has to actually schedule them to
overlap; that’s what asyncio.gather() does.
asyncio.gather() — actually running things concurrently
asyncio.gather() takes multiple coroutines and runs them concurrently,
returning all their results together once every one of them has finished:
Two things to notice. First, all three "calling ... API..." lines print
immediately, one after another — every coroutine starts right away,
rather than waiting for the previous one to finish. Second, the total
time is now roughly 2.0 seconds — the single longest wait, not the
sum of all three. While call_search_api() is in the middle of its
2-second wait, the other two are also waiting, at the same time, instead
of sitting in line — exactly
the overlap Concept 1 identified as the actual goal.
asyncio.gather() is passed each coroutine directly — note
call_search_api() here, calling the function to produce a coroutine
object, not await-ing it individually; gather() itself is what you
await, and it takes care of running all three underneath that one
await.
Results come back in the order you passed them, not the order they finish
results above is ['3 results found', '72F and sunny', '4'] — matching
the order call_search_api, call_weather_api, call_calculator_api
were passed to gather(), even though call_calculator_api() (a
half-second wait) actually finishes first, well before
call_search_api()’s two-second wait completes:
This makes gather()’s results reliably unpackable —
the same tuple-unpacking mechanism from the data structures lesson
— search, weather, calc = await asyncio.gather(...) always lines up
correctly with which call is which, regardless of which one actually
completed fastest behind the scenes.
Rewriting three sequential API calls as coroutines and await-ing each one in turn, without gather() — does this run them concurrently?
Implement load_profile so all three fetches run concurrently via asyncio.gather(), returning their results as a tuple in the order (user, permissions, settings).