Thread backend¶
Use the thread backend for blocking I/O or synchronous libraries that would otherwise block the event loop.
Good uses¶
blocking vendor SDK calls;
file I/O;
synchronous HTTP clients;
synchronous database drivers;
small blocking operations from async services.
Example¶
examples/00_quickstart_thread_backend.py¶
"""
Quickstart: run one blocking sync function from async code.
This is the most basic `leasepool` usage:
1. create a manager
2. start it
3. acquire a lease
4. run sync work inside the leased executor
5. stop the manager
"""
from __future__ import annotations
import asyncio
import time
from leasepool import ExecutorBackend, LeasedExecutorManager
def blocking_uppercase(value: str) -> str:
time.sleep(0.2)
return value.upper()
async def main() -> None:
manager = LeasedExecutorManager(
backend=ExecutorBackend.THREAD,
max_pools=2,
min_pools=1,
workers_per_pool=4,
name_prefix="quickstart-worker",
)
await manager.start()
try:
async with await manager.acquire(owner="quickstart") as lease:
result = await lease.run(blocking_uppercase, "hello leasepool")
print("Result:", result)
finally:
await manager.stop()
if __name__ == "__main__":
asyncio.run(main())
Context manager pattern¶
examples/01_lease_context_manager.py¶
"""
Using ExecutorLease as an async context manager.
This is the safest default style. The lease is automatically returned even if
your work raises an exception.
"""
from __future__ import annotations
import asyncio
import time
from leasepool import LeasedExecutorManager
def blocking_add(left: int, right: int) -> int:
time.sleep(0.1)
return left + right
def blocking_join(*, first: str, second: str) -> str:
time.sleep(0.1)
return f"{first}-{second}"
async def main() -> None:
manager = LeasedExecutorManager(
backend="thread",
max_pools=1,
min_pools=1,
workers_per_pool=2,
)
await manager.start()
try:
print("Before acquire:", manager.stats())
async with await manager.acquire(owner="context-manager-example") as lease:
print("Lease ID:", lease.lease_id)
print("Owner:", lease.owner)
print("Soft expires at monotonic timestamp:", lease.soft_expires_at)
print("Hard expires at monotonic timestamp:", lease.hard_expires_at)
number_result = await lease.run(blocking_add, 20, 22)
keyword_result = await lease.run(
blocking_join,
first="hello",
second="world",
)
print("Number result:", number_result)
print("Keyword result:", keyword_result)
print("During lease:", manager.stats())
print("After context exits:", manager.stats())
finally:
await manager.stop()
if __name__ == "__main__":
asyncio.run(main())
Avoid¶
Do not use the thread backend for heavy pure-Python CPU work if true CPU parallelism is required on Python 3.11. Use the process backend instead.