Недавно попалась интересная задачка по асинхронщине. Допустим, есть синхронная функция, которую нужно запустить в асинхронном коде через пул потоков - и потом отменить. Что в таком коде может пойти не так? 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