파이썬 asyncio 모듈 사용 시 자주 발생하는 문제들
이 글에서는 파이썬 asyncio를 사용하는 개발자들이 자주 겪는 문제들과 그 해결 방법을 다룹니다.
- 작업을 중지하는 방법
asyncio.Task 객체의 cancel() 메서드를 사용하여 작업을 취소할 수 있습니다. 작업이 성공적으로 취소되면 cancel() 메서드는 True를 반환하고, 그렇지 않으면 False를 반환합니다.
# 작업 취소
was_cancelled = task.cancel()
작업이 이미 완료된 경우에는 취소할 수 없으며, cancel() 메서드는 False를 반환하고 작업은 취소 상태가 되지 않습니다.
다음에 작업이 실행될 기회가 생기면 CancelledError 예외가 발생합니다. 만약 CancelledError 예외가 래핑된 코루틴 내에서 처리되지 않으면 작업은 취소됩니다.
하지만 래핑된 코루틴 내에서 CancelledError 예외가 처리된다면 작업은 취소되지 않습니다. cancel() 메서드는 CancelledError의 내용에 사용될 메시지 매개변수도 받을 수 있습니다.
실행 중인 작업을 취소하는 방법을 살펴보겠습니다.
이 예제에서는 메시지를 출력한 후 잠시 대기하는 작업 코루틴을 정의합니다.
그 다음 프로그램 진입점으로 사용될 메인 코루틴을 정의합니다. 메인 코루틴은 메시지를 출력하고 작업을 생성 및 예약한 후 잠시 대기합니다.
그런 다음 메인 코루틴이 실행을 재개하고 작업을 취소합니다. 메인 코루틴은 잠시 더 대기하여 작업이 취소 요청에 응답할 시간을 줍니다. 그 후 메인 코루틴은 작업 취소 요청이 성공했는지 보고합니다.
작업이 취소된 후 완료됩니다. 메인 코루틴은 프로그램을 종료하기 전에 작업이 취소된 상태인지 보고합니다.
# TechPython.com
# 실행 중인 작업 취소 예제
import asyncio
# 작업용 코루틴 정의
async def worker_coroutine():
# 메시지 출력
print('작업 실행 중')
# 잠시 대기
await asyncio.sleep(2)
# 메인 코루틴
async def main():
# 메시지 출력
print('메인 코루틴 시작')
# 작업 생성 및 예약
worker_task = asyncio.create_task(worker_coroutine())
# 잠시 대기
await asyncio.sleep(0.5)
# 작업 취소
cancel_success = worker_task.cancel()
# 취소 요청 성공 여부 출력
print(f'취소 성공: {cancel_success}')
# 잠시 대기
await asyncio.sleep(1)
# 작업 상태 확인
print(f'취소됨: {worker_task.cancelled()}')
# 최종 메시지 출력
print('메인 코루틴 완료')
# asyncio 프로그램 시작
asyncio.run(main())
이 예제를 실행하면 asyncio 이벤트 루프가 시작되고 main() 코루틴이 실행됩니다. main() 코루틴은 메시지를 출력한 다음 작업 코루틴을 생성하고 예약합니다. 그 다음 잠시 대기하여 작업 코루틴이 실행을 시작할 시간을 줍니다. 작업이 실행되어 메시지를 출력하고 잠시 대기합니다.
main() 코루틴이 실행을 재개하고 작업을 취소합니다. 작업 취소 요청이 성공했음을 보고합니다. 그 다음 잠시 대기하여 작업이 취소 요청에 응답할 시간을 줍니다.
worker_coroutine()이 실행을 재개하고 CancelledError 예외를 발생시켜 작업이 실패하고 완료됩니다. main() 코루틴이 실행을 재개하고 작업이 취소된 상태인지 보고합니다. 이 경우에는 그렇습니다.
이 예제는 실행 중인 작업을 취소하는 일반적인 경우를 보여줍니다.
메인 코루틴 시작
작업 실행 중
취소 성공: True
취소됨: True
메인 코루틴 완료
- 작업이 완료될 때까지 기다리는 방법
asyncio.Task 객체를 직접 기다림으로써 작업이 완료될 때까지 기다릴 수 있습니다.
# 작업이 완료될 때까지 기다리기
await task
한 줄에서 작업을 생성하고 기다릴 수 있습니다.
# 작업 생성 및 완료될 때까지 기다리기
await asyncio.create_task(custom_coroutine())
- 작업에서 반환값을 가져오는 방법
코루틴의 값을 호출자에게 반환해야 할 수 있습니다. await를 사용하여 코루틴에서 반환값을 검색할 수 있습니다. 이는 다른 코루틴이 값을 반환한다고 가정합니다.
# 값을 반환하는 코루틴
async def data_coroutine():
return 200
다른 코루틴을 기다리면 호출 코루틴은 일시 중단되고 다른 코루틴이 실행되도록 예약됩니다. 다른 코루틴이 완료되면 호출 코루틴이 다시 실행됩니다. 반환값은 다른 코루틴에서 호출자에게 전달됩니다.
# 코루틴 실행 및 반환값 검색
value = await data_coroutine()
코루틴은 asyncio.Task 객체로 래핑할 수 있습니다. 이는 현재 코루틴이 기다리지 않고 독립적으로 코루틴을 실행하는 데 도움이 됩니다.
이는 asyncio.create_task() 함수를 사용하여 구현할 수 있습니다.
# 코루틴을 작업으로 래핑하고 실행을 예약
task = asyncio.create_task(data_coroutine())
asyncio.Task에서 반환값을 검색하는 두 가지 방법이 있습니다:
- 작업을 기다립니다.
- result() 메서드를 호출합니다.
작업을 기다려서 반환값을 검색할 수 있습니다. 작업이 예약되거나 실행 중인 경우 호출자는 작업이 완료되고 반환값을 제공할 때까지 중단됩니다. 작업이 이미 완료된 경우 반환값이 즉시 제공됩니다.
# 작업에서 반환값 가져오기
value = await task
코루틴과 달리 작업을 여러 번 기다려도 오류가 발생하지 않습니다.
# 작업에서 반환값 가져오기
value = await task
# 작업에서 반환값 다시 가져오기
value = await task
또한 asyncio.Task 객체의 result() 메서드를 호출하여 작업의 반환값을 가져올 수 있습니다.
# 작업에서 반환값 가져오기
value = task.result()
이 작업은 완료되어야 합니다. 그렇지 않으면 InvalidStateError 예외가 발생합니다. 작업이 취소된 경우 CancelledError 예외가 발생합니다.
- 백그라운드에서 작업을 실행하는 방법
코루틴을 asyncio.Task 객체로 래핑하여 백그라운드에서 코루틴을 실행할 수 있습니다. 이는 asyncio.create_task() 함수를 호출하고 코루틴을 전달하여 구현할 수 있습니다.
코루틴은 Task 객체로 래핑되고 실행되도록 예약됩니다. 호출자는 중단되지 않고 작업 객체가 반환됩니다.
# 실행을 위해 작업 예약
worker_task = asyncio.create_task(background_coroutine())
현재 코루틴이 어떤 이유로든 중단되기 전에는 작업이 실행을 시작하지 않습니다. 작업이 실행을 시작할 수 있도록 잠시 중지하여 도울 수 있습니다. 이는 0초 동안 대기하여 구현할 수 있습니다.
# 작업이 실행을 시작할 수 있도록 잠시 중지
await asyncio.sleep(0)
이것은 호출자를 잠시 중지하고 실행을 요청할 기회를 줍니다. 이는 필수적이지 않습니다. 호출자가 나중에 중지되거나 정상 실행의 일부로 종료될 수 있기 때문입니다. 호출자가 할 일이 없을 때 직접 작업을 기다릴 수도 있습니다.
# 작업이 완료될 때까지 기다리기
await worker_task
- 모든 백그라운드 작업을 기다리는 방법
asyncio 프로그램의 모든 독립 작업을 기다릴 수 있습니다. 이는 먼저 asyncio.all_tasks() 함수를 사용하여 현재 실행 중인 모든 작업의 집합을 가져와 구현할 수 있습니다.
# 실행 중인 모든 작업의 집합 가져오기
all_workers = asyncio.all_tasks()
이것은 현재 실행 중인 각 작업에 대한 asyncio.Task 객체가 포함된 집합을 반환합니다. 이는 main() 코루틴을 포함합니다.
이 집합을 직접 기다릴 수 없습니다. 왜냐하면 이 집합에는 현재 작업이 포함되어 있기 때문에 영원히 차단되기 때문입니다. 따라서 현재 실행 중인 작업의 asyncio.Task 객체를 가져와서 집합에서 제거할 수 있습니다.
이는 먼저 asyncio.current_task() 메서드를 호출하여 현재 코루틴의 작업을 가져온 다음 remove() 메서드를 사용하여 집합에서 제거하여 구현할 수 있습니다.
# 현재 작업 가져오기
current_worker = asyncio.current_task()
# 모든 작업 목록에서 현재 작업 제거
all_workers.remove(current_worker)
마지막으로, 나머지 작업 집합을 기다릴 수 있습니다. 이것은 호출자를 중단시켜 집합의 모든 작업이 완료될 때까지 기다립니다.
# 모든 작업이 완료될 때까지 중지
await asyncio.wait(all_workers)
이들을 결합하면, main() 코루틴 끝에 추가된 다음 코드 조각은 모든 백그라운드 작업이 완료될 때까지 기다립니다.
# 실행 중인 모든 작업의 집합 가져오기
all_workers = asyncio.all_tasks()
# 현재 작업 가져오기
current_worker = asyncio.current_task()
# 모든 작업 목록에서 현재 작업 제거
all_workers.remove(current_worker)
# 모든 작업이 완료될 때까지 중지
await asyncio.wait(all_workers)