{
  "id": "async",
  "title": "Asynchronous operations with redis-py",
  "url": "https://redis.io/docs/latest/develop/clients/redis-py/async/",
  "summary": "Use redis-py with asyncio for non-blocking Redis access",
  "tags": [
    "docs",
    "develop",
    "stack",
    "oss",
    "rs",
    "rc",
    "oss",
    "kubernetes",
    "clients"
  ],
  "last_updated": "2026-05-25T10:30:01-07:00",
  "page_type": "content",
  "content_hash": "25b46f42103bb2cb128bdd2382c06d11379c4aedb5308a6e5153f916679d5a9c",
  "sections": [
    {
      "id": "basic-connection",
      "title": "Basic connection",
      "role": "content",
      "text": "Import the async client from `redis.asyncio` and `await` each command.\nConstruction is synchronous and doesn't open a connection — the pool\nestablishes one lazily the first time you issue a command. Call `aclose()`\nwhen you're done to release the underlying socket.\n\nFoundational: Connect to Redis with the async client and run a basic SET/GET\n\n**Difficulty:** Beginner\n\n**Available in:** Python\n\n##### Python\n\n[code example]\n\n\n\nThe recommended pattern is to use the client as an async context manager,\nwhich ensures `aclose()` runs even if an exception is raised:\n\nFoundational: Use the async client as a context manager for automatic cleanup\n\n**Difficulty:** Beginner\n\n**Available in:** Python\n\n##### Python\n\n[code example]"
    },
    {
      "id": "connection-pools",
      "title": "Connection pools",
      "role": "content",
      "text": "For production usage, you should manage connections with a connection pool\nrather than opening and closing them individually.\nSee [Connection pools and multiplexing](https://redis.io/docs/latest/develop/clients/pools-and-muxing)\nfor more information about how this works.\n\nA `Redis` client instance already creates and manages its own connection\npool internally, so in a long-running async application the recommended\npattern is to create a single client at startup, share it across requests\nand tasks, and close it at shutdown. Avoid creating a new `Redis()` per\nrequest — it defeats pooling and pays the connection cost on every call.\n\nFoundational: Create and share a single async client across the app\n\n**Difficulty:** Beginner\n\n**Available in:** Python\n\n##### Python\n\n[code example]\n\n\n\nTune `max_connections` to the maximum number of concurrent Redis operations\nyou expect from the process. If you'd rather block on pool exhaustion than\nraise an error, construct the client with a `BlockingConnectionPool`.\n\n\nDon't share a single `ConnectionPool` across multiple `Redis(connection_pool=...)`\ninstances. Closing any one of those clients also closes the shared pool,\nwhich silently invalidates the connections held by every other client using\nit. Share the `Redis` client object instead."
    },
    {
      "id": "awaiting-commands",
      "title": "Awaiting commands",
      "role": "content",
      "text": "Every command method on the async client returns a coroutine — each call\nmust be `await`ed. Forgetting `await` returns a coroutine object instead of\na result, which is the most common async mistake to watch for.\n\nBecause each command is a coroutine, you can run several concurrently with\n`asyncio.gather()`:\n\nRun several Redis commands concurrently with asyncio.gather\n\n**Difficulty:** Intermediate\n\n**Available in:** Python\n\n##### Python\n\n[code example]\n\n\n\nEach in-flight command consumes one pooled connection while it's executing,\nso size `max_connections` to match your peak concurrency. (If you instantiate\nthe client with `single_connection_client=True`, all commands serialize\nthrough a single connection instead.)"
    },
    {
      "id": "pipelines-and-transactions",
      "title": "Pipelines and transactions",
      "role": "content",
      "text": "Pipelines and transactions work the same way as in the synchronous client\n(see [Pipelines and transactions](https://redis.io/docs/latest/develop/clients/redis-py/transpipe)\nfor the conceptual background). The only difference is that you create the\npipeline inside an `async with` block and `await pipe.execute()`.\n\nFoundational: Execute commands in an async pipeline\n\n**Difficulty:** Beginner\n\n**Available in:** Python\n\n##### Python\n\n[code example]\n\n\n\n`WATCH`/`MULTI`/`EXEC` for optimistic locking also has an async form.\n`watch()` and `execute()` are coroutines; `multi()` remains synchronous\nbecause it only toggles internal pipeline state.\n\nOptimistic locking with an async pipeline and WATCH\n\n**Difficulty:** Intermediate\n\n**Available in:** Python\n\n##### Python\n\n[code example]"
    },
    {
      "id": "pub-sub",
      "title": "Pub/Sub",
      "role": "content",
      "text": "The async pub/sub object follows the same shape as the sync version. Call\n`await pubsub.subscribe(...)` to register channels, then iterate messages\nwith `async for message in pubsub.listen():`. Use the `PubSub` object as an\nasync context manager (`async with r.pubsub() as pubsub:`) or call\n`await pubsub.aclose()` explicitly to release the connection.\n\nSubscribe and receive messages with the async pub/sub API\n\n**Difficulty:** Intermediate\n\n**Available in:** Python\n\n##### Python\n\n[code example]\n\n\n\nA single `PubSub` object isn't safe to share across tasks — give each\nconsuming task its own subscription."
    },
    {
      "id": "cluster-connections",
      "title": "Cluster connections",
      "role": "content",
      "text": "To connect to a Redis cluster asynchronously, import `RedisCluster` from\n`redis.asyncio.cluster`. The API matches the synchronous cluster client\n(see [Connect to a Redis cluster](https://redis.io/docs/latest/develop/clients/redis-py/connect#connect-to-a-redis-cluster)),\nwith `await` in front of each command.\n\nFoundational: Connect to a Redis cluster with the async client\n\n**Difficulty:** Beginner\n\n**Available in:** Python\n\n##### Python\n\n[code example]"
    },
    {
      "id": "cleanup-and-lifecycle",
      "title": "Cleanup and lifecycle",
      "role": "content",
      "text": "Always close clients and pools when you're done:\n\n- Use `async with Redis(...) as r:` whenever the client's lifetime fits a\n  single scope.\n- For longer-lived clients, call `await r.aclose()` explicitly. (The older\n  `close()` method is deprecated.)\n- For frameworks with startup/shutdown hooks — for example FastAPI's\n  `lifespan` — create the client or pool at startup and close it at\n  shutdown so connections aren't leaked between process restarts."
    },
    {
      "id": "cancellation-and-timeouts",
      "title": "Cancellation and timeouts",
      "role": "content",
      "text": "You can cancel an in-flight command by wrapping it in `asyncio.wait_for()`\n(or `asyncio.timeout()` on Python 3.11+). If the command is canceled\nmid-flight, `redis-py` disconnects the underlying connection to avoid\nresponse/request misalignment on subsequent reads. The next command\ntransparently picks up a new connection from the pool.\n\nApply an asyncio timeout to a Redis command\n\n**Difficulty:** Intermediate\n\n**Available in:** Python\n\n##### Python\n\n[code example]\n\n\n\nA `Redis` client instance is safe to share across tasks (the pool handles\nconcurrency), but stateful objects derived from it — pipelines and pub/sub\nsubscriptions — are not. Give each task its own pipeline or `PubSub`."
    },
    {
      "id": "translating-sync-examples",
      "title": "Translating sync examples",
      "role": "content",
      "text": "To adapt any synchronous example elsewhere in this guide to the async\nclient, apply these rules:\n\n- Replace `import redis` with `import redis.asyncio as redis`.\n- Wrap the example in an `async def` function and call it with\n  `asyncio.run(...)`.\n- Add `await` in front of every command call. (Exception: buffered pipeline\n  commands such as `pipe.set(...)` stay un-awaited; only `pipe.watch(...)`,\n  any reads issued before `pipe.multi()`, and `pipe.execute()` are awaited.)\n- Replace `with r.pipeline(...) as pipe:` with\n  `async with r.pipeline(...) as pipe:`, and `await pipe.execute()`.\n- Replace `r.close()` with `await r.aclose()`, or use\n  `async with redis.Redis(...) as r:` as a context manager."
    },
    {
      "id": "more-information",
      "title": "More information",
      "role": "content",
      "text": "- The [`redis-py` asyncio examples](https://redis.readthedocs.io/en/stable/examples/asyncio_examples.html)\n  on Read the Docs cover further patterns.\n- See [Error handling](https://redis.io/docs/latest/develop/clients/redis-py/error-handling) and\n  [Client-side geographic failover](https://redis.io/docs/latest/develop/clients/redis-py/failover) for\n  resiliency patterns that apply to both sync and async clients."
    }
  ],
  "examples": [
    {
      "id": "basic-connection-ex0",
      "language": "python",
      "code": "import redis.asyncio as redis\n\nasync def basic_example():\n    r = redis.Redis(host='localhost', port=6379, decode_responses=True)\n    await r.set('foo', 'bar')\n    value = await r.get('foo')\n    print(value)\n    # bar\n    await r.aclose()\n\nasyncio.run(basic_example())",
      "section_id": "basic-connection"
    },
    {
      "id": "basic-connection-ex1",
      "language": "python",
      "code": "async def context_example():\n    async with redis.Redis(\n        host='localhost', port=6379, decode_responses=True\n    ) as r:\n        await r.set('foo', 'bar')\n        value = await r.get('foo')\n        print(value)\n        # bar\n\nasyncio.run(context_example())",
      "section_id": "basic-connection"
    },
    {
      "id": "connection-pools-ex0",
      "language": "python",
      "code": "async def shared_client_example():\n    # Create one client at startup. It owns its own internal pool,\n    # which is sized by max_connections.\n    r = redis.Redis(\n        host='localhost', port=6379, decode_responses=True,\n        max_connections=10,\n    )\n    try:\n        # Share `r` across every request and task for the app's lifetime.\n        await r.set('foo', 'bar')\n        value = await r.get('foo')\n        print(value)\n        # bar\n    finally:\n        # Close the client at shutdown to release pooled connections.\n        await r.aclose()\n\nasyncio.run(shared_client_example())",
      "section_id": "connection-pools"
    },
    {
      "id": "awaiting-commands-ex0",
      "language": "python",
      "code": "async def gather_example():\n    async with redis.Redis(\n        host='localhost', port=6379, decode_responses=True\n    ) as r:\n        await r.mset({'a': '1', 'b': '2', 'c': '3'})\n        a, b, c = await asyncio.gather(\n            r.get('a'),\n            r.get('b'),\n            r.get('c'),\n        )\n        print(a, b, c)\n        # 1 2 3\n\nasyncio.run(gather_example())",
      "section_id": "awaiting-commands"
    },
    {
      "id": "pipelines-and-transactions-ex0",
      "language": "python",
      "code": "async def pipeline_example():\n    async with redis.Redis(\n        host='localhost', port=6379, decode_responses=True\n    ) as r:\n        async with r.pipeline(transaction=True) as pipe:\n            pipe.set('a', '1')\n            pipe.set('b', '2')\n            pipe.get('a')\n            pipe.get('b')\n            results = await pipe.execute()\n            print(results)\n            # [True, True, '1', '2']\n\nasyncio.run(pipeline_example())",
      "section_id": "pipelines-and-transactions"
    },
    {
      "id": "pipelines-and-transactions-ex1",
      "language": "python",
      "code": "from redis.exceptions import WatchError\n\nasync def watch_example():\n    async with redis.Redis(\n        host='localhost', port=6379, decode_responses=True\n    ) as r:\n        await r.set('counter', '0')\n        async with r.pipeline(transaction=True) as pipe:\n            while True:\n                try:\n                    await pipe.watch('counter')\n                    current = int(await pipe.get('counter'))\n                    pipe.multi()\n                    pipe.set('counter', str(current + 1))\n                    await pipe.execute()\n                    break\n                except WatchError:\n                    continue\n        print(await r.get('counter'))\n        # 1\n\nasyncio.run(watch_example())",
      "section_id": "pipelines-and-transactions"
    },
    {
      "id": "pub-sub-ex0",
      "language": "python",
      "code": "async def pubsub_example():\n    async with redis.Redis(\n        host='localhost', port=6379, decode_responses=True\n    ) as r:\n        async with r.pubsub() as pubsub:\n            await pubsub.subscribe('channel-1')\n\n            async def reader():\n                async for message in pubsub.listen():\n                    if message['type'] == 'message':\n                        print(message['data'])\n                        # hello\n                        break\n\n            reader_task = asyncio.create_task(reader())\n            await asyncio.sleep(0.1)\n            await r.publish('channel-1', 'hello')\n            await reader_task\n\nasyncio.run(pubsub_example())",
      "section_id": "pub-sub"
    },
    {
      "id": "cluster-connections-ex0",
      "language": "python",
      "code": "from redis.asyncio.cluster import RedisCluster\n\nasync def cluster_example():\n    rc = RedisCluster(host='localhost', port=16379, decode_responses=True)\n    await rc.set('foo', 'bar')\n    value = await rc.get('foo')\n    print(value)\n    # bar\n    await rc.aclose()\n\nasyncio.run(cluster_example())",
      "section_id": "cluster-connections"
    },
    {
      "id": "cancellation-and-timeouts-ex0",
      "language": "python",
      "code": "async def timeout_example():\n    async with redis.Redis(\n        host='localhost', port=6379, decode_responses=True\n    ) as r:\n        try:\n            # BLPOP blocks waiting for a value, so the timeout reliably fires.\n            await asyncio.wait_for(r.blpop('empty-queue'), timeout=0.1)\n        except asyncio.TimeoutError:\n            print('command canceled')\n\nasyncio.run(timeout_example())",
      "section_id": "cancellation-and-timeouts"
    }
  ]
}
