asyncio
The asyncio project on PyPI is a placeholder, not a library. It began as a backport of the asyncio module for Python versions before 3.4, which stopped being useful the moment asyncio shipped in the standard library in 2014. Version 4.0.0, published in August 2025, contains no code at all; its own description says the release exists only to prevent accidental installation of outdated backports and tells you to use the implementation that comes with Python. The thing you actually want, the event loop, coroutines, tasks and streams, is already installed with your interpreter and needs no dependency.
This distribution is a tombstone: the current release contains no code and exists to stop people installing a 2015 backport of a standard library module. Remove it from your requirements and import asyncio from the interpreter you already have.
Use it if
- You are auditing a requirements file, found asyncio pinned in it, and want confirmation that removing the line is safe on any supported Python
- You maintain a package whose install instructions still mention this distribution and need the reference that says it is empty
- You are looking for how to use the standard library module, which is what the patterns below cover, and got here by searching the package name
- You need to explain to a teammate why pip install asyncio appears to succeed and yet changes nothing about their program
- You want asyncio itself, which has been in the standard library since Python 3.4 and needs no package: import asyncio works out of the box
- You are adding it to requirements.txt or pyproject.toml, since the newest release ships no modules and older releases install a decade-old copy of a stdlib module
- You are on Python 3.3 or older, the only case the original backport was for, which no supported interpreter matches today
- You want structured concurrency with a stronger cancellation story than the stdlib gives you, which is what Trio and AnyIO exist for
- You expect maintenance from this repository: python/asyncio has been archived since 2021, its README says the repository is closed, and its default branch is named redirect
- You are chasing a performance problem in the event loop, since installing anything from PyPI under this name will not change how your program runs
Setup reality
There is no setup, and that is the whole point of the entry. pip install asyncio completes without error, which is exactly why it keeps ending up in requirements files: it looks like it worked. On Python 3.4 and later, nothing you install under this name affects the asyncio you import, because the standard library module always wins the import. The one way this package can hurt you is by installing an old release. Versions 3.4.1 through 3.4.3, from 2015, contain a real copy of the module from that era, and depending on your environment layout that copy can shadow the interpreter's own, which produces bewildering failures against modern code. Version 4.0.0 was published precisely to stop that, since a fresh install now resolves to an empty distribution. Practical advice is short: delete the requirement, then import asyncio and check nothing changes. If a transitive dependency pins it, look at whether that dependency has been touched since 2015 before worrying about the pin. The links in the package metadata point where you should be reading anyway: the module documentation at docs.python.org and the historical repository at python/asyncio, which has been archived since April 2021 and whose README directs bug reports to CPython. For the real module, the version of Python matters far more than any package version. TaskGroup, asyncio.timeout and Runner all arrived in 3.11 and changed how correct asyncio code is written; asyncio.to_thread arrived in 3.9; and get_event_loop moved from quietly creating a loop, to warning in 3.12, to raising in 3.14. Code copied from a pre-3.11 tutorial will run and still be the wrong shape.
Patterns
Take it out of your requirementsremove-the-dependency
# requirements.txt
-asyncio==3.4.3
# nothing else changes; the module is part of Python
import asyncio
print(asyncio.__file__) # .../lib/python3.12/asyncio/__init__.pyPrint __file__ after removing it. If the path is inside your interpreter's lib directory rather than site-packages, the standard library is what you were using all along.
Start an event loop the current wayrun-a-coroutine
import asyncio
async def main():
await asyncio.sleep(0.1)
return "done"
if __name__ == "__main__":
print(asyncio.run(main()))asyncio.run creates a loop, runs the coroutine and closes the loop. Since Python 3.7 there is no reason to touch get_event_loop or run_until_complete in application code.
Run tasks concurrently with structured concurrencytask-group
import asyncio
async def main():
async with asyncio.TaskGroup() as tg:
a = tg.create_task(fetch("a"))
b = tg.create_task(fetch("b"))
return a.result(), b.result()
asyncio.run(main())Requires Python 3.11. If one task raises, the rest are cancelled and the group raises an ExceptionGroup, so catch it with except* rather than a plain except.
Collect results on older interpretersgather-results
import asyncio
async def main():
results = await asyncio.gather(fetch("a"), fetch("b"), return_exceptions=True)
for r in results:
if isinstance(r, Exception):
log(r)
asyncio.run(main())gather is the pre-3.11 option. Without return_exceptions the first failure propagates while the other coroutines keep running unsupervised, which is the gap TaskGroup closes.
Bound how long a block may taketimeout
import asyncio
async def main():
try:
async with asyncio.timeout(5):
await slow_call()
except TimeoutError:
return None
asyncio.run(main())asyncio.timeout arrived in 3.11 and raises the builtin TimeoutError. On 3.10 and earlier use asyncio.wait_for, which takes the awaitable rather than wrapping a block.
Move blocking work off the loopblocking-call-offload
import asyncio
async def main():
data = await asyncio.to_thread(open("big.csv").read)
return len(data)
asyncio.run(main())asyncio.to_thread needs 3.9. A blocking call made directly inside a coroutine stalls every other task on that loop, which is the most common cause of a slow async program.
Hand work between tasks with a queueproducer-consumer
import asyncio
async def worker(q):
while True:
item = await q.get()
try:
await handle(item)
finally:
q.task_done()
async def main():
q = asyncio.Queue(maxsize=100)
workers = [asyncio.create_task(worker(q)) for _ in range(4)]
for item in source():
await q.put(item)
await q.join()
for w in workers:
w.cancel()
asyncio.run(main())maxsize is the backpressure. Cancel the workers after join, or asyncio.run will warn about tasks still pending when the loop closes.
Clean up when a task is cancelledhandle-cancellation
import asyncio
async def job():
try:
await long_running()
except asyncio.CancelledError:
await release_resources()
raiseRe-raise CancelledError. Swallowing it makes the task uncancellable and leaves TaskGroup and timeout blocks waiting on work they already gave up on.
Hold on to background taskskeep-task-references
import asyncio
background = set()
def spawn(coro):
task = asyncio.create_task(coro)
background.add(task)
task.add_done_callback(background.discard)
return taskThe event loop keeps only a weak reference to a task. Without a strong reference somewhere, a background task can be garbage collected mid-flight and simply disappear.
Stop calling get_event_loopavoid-get-event-loop
import asyncio
# inside a coroutine, when you really need the loop object
loop = asyncio.get_running_loop()
# not this: warns on 3.12, raises RuntimeError on 3.14
# loop = asyncio.get_event_loop()get_running_loop has been the correct call since 3.7 and fails loudly outside a running loop instead of quietly creating a second one.
Serve connections with the streams APIrun-a-server
import asyncio
async def handle(reader, writer):
data = await reader.readline()
writer.write(data.upper())
await writer.drain()
writer.close()
await writer.wait_closed()
async def main():
server = await asyncio.start_server(handle, "127.0.0.1", 8888)
async with server:
await server.serve_forever()
asyncio.run(main())await writer.drain() is the backpressure hook; skipping it lets the write buffer grow without limit when a client reads slowly.
Use a faster event loop without changing your codeswap-the-loop
import asyncio
import uvloop
async def main():
await work()
uvloop.run(main()) # or asyncio.set_event_loop_policy(uvloop.EventLoopPolicy())uvloop is a separate package and is not available on Windows. Your application code stays as it is; only the loop implementation changes.
Alternatives
| Package | Registry | Pick it when |
|---|---|---|
| anyio | PyPI | You want structured concurrency and cancellation scopes over the stdlib loop, and want the same code to run on Trio as well |
| trio | PyPI | You are starting fresh and want nurseries and cancellation designed in from the beginning rather than added in 3.11 |
| uvloop | PyPI | You are keeping stdlib asyncio and want a faster event loop implementation underneath it on Linux or macOS |