Uso FastAPI para desarrollar API de capa de datos que acceden a SQL Server. No importa el uso de pytds o pyodbc, si hay una transacción de la base de datos que provocó el bloqueo de cualquier solicitud, todas las demás solicitudes se bloquearían. (incluso sin operación de base de datos)
Reproducir:
INSERT INTO [dbo].[KVStore] VALUES ('1', '1', 0) begin tran SET TRANSACTION ISOLATION LEVEL Serializable SELECT * FROM [dbo].[KVStore] def kv_delete_by_key_2_sql(): conn = pytds.connect(dsn='192.168.0.1', database=cfg.kvStore_db, user=cfg.kvStore_uid, password=cfg.kvStore_upwd, port=1435, autocommit=True) engine = conn.cursor() try: sql = "delete KVStore; commit" with concurrent.futures.ThreadPoolExecutor() as executor: future = executor.submit(engine.execute, sql) rs = future.result() j = { 'success': True, 'rowcount': rs.rowcount } return jsonable_encoder(j) except Exception as exn: j = { 'success': False, 'reason': exn_handle(exn) } return jsonable_encoder(j) @app.post("/kvStore/delete") async def kv_delete(request: Request, type_: Optional[str] = Query(None, max_length=50)): request_data = await request.json() return kv_delete_by_key_2_sql() async def hangit0(request: Request, t: int = Query(0)): print(t, datetime.utcnow().strftime('%Y-%m-%d %H:%M:%S.%f')[:-3]) await asyncio.sleep(t) print(t, datetime.utcnow().strftime('%Y-%m-%d %H:%M:%S.%f')[:-3]) j = { 'success': True } return jsonable_encoder(j) @app.get("/kvStore/hangit/") async def hangit(request: Request, t: int = Query(0)): return await hangit0(request, t)Esperaba que el paso 2 se colgara y que el paso 3 volviera directamente después de 2 segundos. Sin embargo, el paso 3 nunca regresa si la transacción no se confirma o retrocede ...
¿Cómo hago que estas funciones de controlador funcionen simultáneamente?
La razón es que rs = future.result() es en realidad una llamada de bloqueo; consulte la documentación de Python . Desafortunadamente, executor.submit() no devuelve un objeto awaitable ( concurrent.futures.Future es diferente de asyncio.Future .
Puede usar asyncio.wrap_future que toma concurrent.futures.Future y devuelve asyncio.Future (consulte la documentación de Python ). El nuevo objeto Future está disponible, por lo que puede convertir su función de bloqueo en una función asíncrona.
Un ejemplo :
import asyncio import concurrent.futures async def my_async(): with concurrent.futures.ThreadPoolExecutor() as executor: future = executor.submit(lambda x: x + 1, 1) return await asyncio.wrap_future(future) print(asyncio.run(my_async())) En su código, simplemente cambie rs = future.result() a rs = await asyncio.wrap_future(future) y haga que toda la función sea async . Eso debería hacer la magia, ¡buena suerte! :)