Skip to content

How-to: choosing a concurrency pattern

A guide consolidating the findings of FT188 (threading) through FT192 (asyncio). In nene2-python the UseCase layer is HTTP-independent, so the choice of concurrency is made in the UseCase or the HTTP handler layer.


Quick selection table

Use caseRecommendedFT
Waiting on I/O inside a FastAPI handlerasync def + awaitFT192
Run a sync UseCase non-blockinglyAsyncUseCaseProtocolFT6, FT14
Offload CPU-bound work to a threadasyncio.to_thread() or ThreadPoolExecutorFT188, FT191
Offload CPU-bound work to a processProcessPoolExecutor / multiprocessingFT190, FT191
External command in the backgroundsubprocess (shell=False + allowlist)FT189
Shared in-memory cacheTtlCache[V] (thread-safe)FT119, FT171

asyncio (FT192)

FastAPI routes are async def by default. To parallelize multiple I/O operations:

python
import asyncio

async def execute(self, input_: ListNotesInput) -> ListNotesOutput:
    items_task = asyncio.create_task(self._repository.find_all_async(...))
    total_task = asyncio.create_task(self._repository.count_async())
    items, total = await asyncio.gather(items_task, total_task)
    return ListNotesOutput(items=items, total=total, ...)

Note: Pydantic v2 truncates float to int. Make numeric bounds explicit with Field(ge=..., le=...).


threading (FT188)

Under the GIL this is unsuited to CPU parallelism, but it is useful as a bridge to legacy APIs whose blocking I/O hasn't been made async.

  • threading.Lock / asyncio.Lock — protect shared state
  • ThreadPoolExecutor — offload sync functions (combine with FT191)

subprocess (FT189)

Mandatory rules (CLAUDE.md / FT189 security diagnosis):

  1. shell=False only
  2. Validate the command name against an allowlist
  3. Set a timeout and an stdout size limit
  4. # noqa: S603 for ruff only after the allowlist check (state the reason in the docstring)

Alignment with nene2

LayerConcurrency
UseCaseAsyncUseCaseProtocol or pure-sync (InMemory testable)
HTTPasync def handlers, BackgroundTasks (background-tasks.md)
Middlewaresync ASGI; don't put blocking work inside middleware
MCPexpose the UseCase as a tool directly — concurrency stays inside the UseCase

Detailed reports: FT188 · FT189 · FT190 · FT191 · FT192

Released under the MIT License.