Skip to content

Async vs sync

fastgrpc auto-detects whether to run on the async (grpc.aio) or sync (grpc.server thread-pool) gRPC stack based on your handler signatures.

The rule

Handlers Server stack
Any async def grpc.aio.server()
All plain def grpc.server(ThreadPoolExecutor)
Mixed ValidationError raised at startup

The CLI prints which stack was chosen on startup:

✓ Serving on grpc://127.0.0.1:50051 (async)

or

✓ Serving on grpc://127.0.0.1:50051 (sync)

Why no mixing

The two grpcio stacks are not interoperable. A single server cannot host both sync handlers (which run on a thread pool) and async handlers (which run on an event loop). fastgrpc fails fast with a message that names every offending method:

ValidationError: cannot mix async and sync RPC methods in the same app:
  async: UserService.async_method
  sync:  UserService.sync_method

If you need both, run two services on different ports.

When to pick each

Use async when...

  • You call other async APIs (databases like asyncpg, HTTP via httpx, other gRPC services)
  • You expect high concurrency with mostly I/O-bound handlers
  • You want the standard FastAPI-style ergonomics
@service
class UserService:
    @rpc
    async def get_user(self, user_id: int) -> User:
        async with db.session() as s:
            row = await s.fetch_one("SELECT ...", user_id)
        return User(**row)

Use sync when...

  • Your business logic is CPU-bound (data crunching, ML inference)
  • You're calling sync libraries that don't have async equivalents
  • You're integrating into an existing sync codebase
@service
class InferenceService:
    @rpc
    def predict(self, features: list[float]) -> Prediction:
        result = model.run(features)  # blocking, CPU-bound
        return Prediction(score=result.score)

Performance characteristics

sync stack async stack
Underlying executor thread pool (max_workers in App) asyncio event loop
Best for CPU-bound, blocking I/O via threads high-concurrency I/O
GIL impact One thread per concurrent request One coroutine per concurrent request, single thread
Cold-start latency Lower Slightly higher (event loop setup)

Both stacks share the same gRPC C core for the wire protocol, so the difference is in the executor model, not the network layer.