Недавно попалась интересная задачка по асинхронщине. Допустим, есть синхронная функция, которую нужно запустить в асинхронном коде через пул потоков - и потом отменить. Что в таком коде может пойти не так? import asyncio import time

def sync_func() -> None: print("Starting sync function") time.sleep(2) print("Sync function completed")

async def main() -> None: task = asyncio.create_task(asyncio.to_thread(sync_func))

await asyncio.sleep(0.5)

task.cancel()

try: await task except asyncio.CancelledError: print("Task cancelled")

await asyncio.sleep(3)

asyncio.run(main()) Ответ - функция одновременно отменится и нет: Starting sync function Task cancelled Sync function completed Суть в том, как работает task.cancel() — он отменяет не работу, а ожидание задачи. Сама функция в это время выполняется в отдельном потоке ОС, а поток в Python принудительно остановить нельзя: можно только попросить его завершиться кооперативно.

CancelledError выбрасывается в корутине в точке await, но у синхронной функции await нет — event loop физически не может «дотянуться» до чужого потока и внедрить туда исключение.

Единственный случай, когда отмена срабатывает полностью, — если успеть до того, как пул потоков начнёт выполнять функцию (пока она ещё лежит в очереди).

Правильное решение - кооперативная отмена через флаг: import asyncio import time from threading import Event

stop = Event()

def sync_func(stop: Event) -> None: print("Starting sync function") deadline = time.monotonic() + 2 while time.monotonic() < deadline: if stop.is_set(): print("Stopped early") return time.sleep(0.2)

print("Sync function completed")

async def main() -> None: task = asyncio.create_task(asyncio.to_thread(sync_func, stop)) await asyncio.sleep(0.5)

stop.set() # просим поток остановиться task.cancel() # отменяем только ожидание

try: await task except asyncio.CancelledError: print("Task cancelled")

asyncio.run(main()) Теперь поток завершается сам: Starting sync function Task cancelled Stopped early