1event loop 내 n task
하나의 스레드에는 하나의 event loop 만 실행가능
cannot schedule new futures after interpreter shutdown
RuntimeError: Cannot close a running event loop
task.cancel()
await task > 무한 대기
없으면
RuntimeError: Cannot close a running event loop
그리고 실행하면
cannot schedule new futures after interpreter shutdown
✔ 질문 요약:
- loop1 = create_safe_loop() 실행 후
- loop2 = create_safe_loop() 실행하면
- 두 번째 create_safe_loop() 실행 시 이미 존재하는 이벤트 루프(loop1)가 반환되지 않는가?
- 아직 스레드가 나뉘지 않았으므로, 같은 스레드에서 asyncio.get_running_loop()가 loop1을 반환하는 것 아닌가?
- 같은 스레드에서 실행된다면, asyncio.get_running_loop()는 loop1을 반환함.
- 즉, loop2 = create_safe_loop()를 실행할 때 같은 스레드에서 실행되므로, 이미 실행된 loop1이 반환될 가능성이 있음.
💡 왜?
- asyncio.get_running_loop()는 현재 실행 중인 이벤트 루프를 반환.
- 같은 스레드에서 loop1이 asyncio.set_event_loop(loop1)으로 설정되었으면,
이후 asyncio.get_running_loop()를 호출하면 loop1이 반환됨.
✔ 즉, loop1 = create_safe_loop() 실행 후 loop2 = create_safe_loop()를 실행하면 loop2는 loop1과 동일한 이벤트 루프가 될 가능성이 있음.
📌 2. 해결 방법: 스레드를 먼저 생성하고 loop를 분리해야 함
🔹 loop1과 loop2가 동일한 이벤트 루프가 되지 않도록 하려면,
각각 다른 스레드에서 create_safe_loop()를 실행해야 함.
🔹 즉, loop1을 생성한 후 t1.start()로 실행한 다음 loop2를 생성해야 함.
✔ 질문 요약:
- create_safe_loop()와 run_loop()에서 둘 다 asyncio.set_event_loop(loop)를 호출하고 있음.
- 이것이 중복 등록이 되는가?
📌 1. asyncio.set_event_loop(loop)의 역할과 범위
🔹 asyncio.set_event_loop(loop)는 현재 실행 중인 스레드에서 사용할 기본 이벤트 루프를 설정하는 함수.
🔹 즉, asyncio.get_running_loop()를 호출하면 이 함수로 설정한 이벤트 루프가 반환됨.
🔹 하지만, 각 스레드는 독립적인 실행 환경을 가지므로, 스레드마다 set_event_loop(loop)를 호출해야 함.
📌 2. create_safe_loop()에서 set_event_loop(loop)가 필요한가?
def create_safe_loop():
try:
loop = asyncio.get_running_loop() # ✅ 현재 실행 중인 루프 확인
if loop.is_closed():
loop = asyncio.new_event_loop()
asyncio.set_event_loop(loop) # ✅ 현재 스레드에서 기본 루프 설정
except RuntimeError:
loop = asyncio.new_event_loop()
asyncio.set_event_loop(loop) # ✅ 현재 스레드에서 기본 루프 설정
return loop
✔ 현재 실행 중인 루프(get_running_loop())가 없거나 닫혀 있으면 새로운 루프를 생성하고, 이를 현재 스레드의 기본 루프로 설정.
✔ 하지만, 이 함수는 main() 함수가 실행되는 메인 스레드에서만 실행됨.
✔ 즉, create_safe_loop()는 이벤트 루프를 생성하는 역할을 하며, 이후 다른 스레드에서 실행되는 루프와는 관계가 없음.
📌 3. run_loop()에서 set_event_loop(loop)가 필요한가?
def run_loop(loop):
asyncio.set_event_loop(loop) # ✅ 현재 실행 중인 스레드에서 이벤트 루프 설정
loop.run_forever()
✔ 여기서 asyncio.set_event_loop(loop)는 반드시 필요함!
✔ 이벤트 루프는 create_safe_loop()에서 만든 것이지만, 그것은 main()이 실행되는 스레드에서 설정된 것.
✔ 새로운 스레드에서 run_loop()를 실행할 때, 해당 스레드에서 loop를 기본 루프로 설정하지 않으면 asyncio.get_running_loop()를 호출할 수 없음.
✅ 4. create_safe_loop()에서 set_event_loop(loop)를 제거할 수 있는가?
✔ 만약 create_safe_loop()에서 asyncio.set_event_loop(loop)를 제거하면, main()에서 asyncio.get_running_loop()를 호출할 수 없게 됨.
✔ 즉, main()에서 실행되는 async 함수들이 실행될 기본 루프가 설정되지 않음.
✔ 따라서, create_safe_loop()에서 set_event_loop(loop)는 그대로 유지하는 것이 좋음.
✅ 5. run_loop()에서 set_event_loop(loop)를 제거할 수 있는가?
✔ 아니요, run_loop()에서 set_event_loop(loop)를 제거하면 해당 스레드에서 loop를 기본 루프로 설정할 수 없음.
✔ 즉, asyncio.get_running_loop()를 호출하면 RuntimeError: There is no current event loop 오류가 발생함.
✔ 따라서, run_loop()에서 set_event_loop(loop)를 반드시 유지해야 함.
✅ 6. 결론
함수set_event_loop(loop) 필요 여부이유
create_safe_loop() | ✅ 필요 | 메인 스레드에서 실행될 루프를 설정해야 함 |
run_loop() | ✅ 필요 | 새 스레드에서 실행될 루프를 설정해야 함 |
🚀 최종 정리
✅ create_safe_loop()에서는 asyncio.set_event_loop(loop)를 호출해야 메인 스레드에서 asyncio.get_running_loop()가 가능함.
✅ run_loop()에서는 asyncio.set_event_loop(loop)를 호출해야 새 스레드에서 asyncio.get_running_loop()가 가능함.
✅ 즉, 두 곳에서 asyncio.set_event_loop(loop)를 호출하는 것은 중복이 아니라 필수적인 과정. 🚀
✅ Cannot close a running event loop 오류가 떠도 괜찮은가?
✔ 결론: 일반적으로 괜찮지 않습니다.
✔ 이 오류는 이벤트 루프가 실행 중인 상태에서 loop.close()를 호출할 때 발생합니다.
✔ 정상적인 종료 과정에서 이 오류가 발생했다면, 루프를 제대로 정리하지 않았다는 신호일 가능성이 큽니다.
✔ 다만, 서비스 종료 직전이라면 무시해도 동작에는 영향을 미치지 않을 수 있습니다.
📌 1. 오류 원인: loop.close()를 호출했지만 루프가 아직 실행 중
🔹 이 오류가 발생하는 대표적인 상황
- 아직 실행 중인 Task가 남아 있음 → loop.stop()을 호출하기 전에 Task가 끝나지 않음.
- 다른 스레드에서 실행 중인 루프를 닫으려고 함 → asyncio.get_running_loop()가 반환하는 루프가 현재 스레드에서 실행 중이면 닫을 수 없음.
- asyncio.run() 내부에서 loop.close()를 직접 호출 → asyncio.run()이 자동으로 루프를 닫으므로, loop.close()를 별도로 호출하면 오류가 발생할 수 있음.
✅ 2. 해결 방법: loop.close() 호출 전에 loop.stop()을 먼저 실행
🔹 루프를 닫기 전에 반드시 실행 중인 Task를 모두 정리해야 함. 🔹 Task를 취소한 후, 루프를 stop()하고 종료해야 함.
🔹 올바른 루프 종료 코드
import asyncio
async def task():
while True:
print("Running task...")
await asyncio.sleep(1)
async def main():
loop = asyncio.get_running_loop()
t = asyncio.create_task(task())
await asyncio.sleep(5) # ✅ 5초 후 종료
t.cancel()
try:
await t # ✅ Task가 정상 종료될 때까지 대기
except asyncio.CancelledError:
print("Task was cancelled.")
loop.stop() # ✅ 먼저 루프 중지
loop.close() # ✅ 그 후 루프 종료
asyncio.run(main()) # ✅ 실행
✔ 이제 Cannot close a running event loop 오류 없이 안전하게 종료됨.
✔ loop.stop()을 먼저 실행하고, 실행 중인 Task가 종료된 후 loop.close()를 호출해야 함.
✅ 3. asyncio.run()을 사용할 때 loop.close()를 직접 호출하면 안 됨
🔹 asyncio.run()은 내부적으로 루프를 자동으로 닫음. 🔹 즉, asyncio.run(main())을 실행한 후 loop.close()를 직접 호출하면 오류가 발생할 수 있음.
async def main():
print("Asyncio is running!")
await asyncio.sleep(1)
asyncio.run(main()) # ✅ `loop.close()`를 직접 호출하지 않아야 함.
✔ 따라서 asyncio.run() 내부에서 실행된 루프를 직접 닫을 필요가 없음.
✅ 4. 멀티 스레드 환경에서 Cannot close a running event loop가 발생하는 경우
🔹 다른 스레드에서 실행 중인 루프를 loop.close()로 닫으려고 하면 오류 발생. 🔹 해결 방법: loop.call_soon_threadsafe()를 사용하여 안전하게 종료.
import asyncio
import threading
def run_loop(loop):
asyncio.set_event_loop(loop)
loop.run_forever()
loop = asyncio.new_event_loop()
t = threading.Thread(target=run_loop, args=(loop,), daemon=True)
t.start()
# ✅ 안전한 종료 방법
def shutdown():
print("Stopping loop...")
loop.stop()
loop.call_soon_threadsafe(loop.close)
shutdown()
✔ 따라서 asyncio.run() 내부에서 실행된 루프를 직접 닫을 필요가 없음.
🚀 결론
✅ 이 오류가 발생하면 일반적으로 문제가 있는 것이므로 수정하는 것이 좋음.
✅ 루프를 닫기 전에 반드시 loop.stop()을 먼저 호출해야 함.
✅ asyncio.run() 내부에서 loop.close()를 직접 호출하지 않아야 함.
✅ 멀티 스레드 환경에서는 loop.call_soon_threadsafe(loop.close)를 사용하여 안전하게 종료해야 함. 🚀
✅ RuntimeError: Cannot close a running event loop가 call_soon_threadsafe(loop.close) 이후에도 발생하는 이유 및 해결 방법
✔ 이벤트 루프가 loop.run_forever()로 실행 중인 상태에서 loop.close()를 호출하면 RuntimeError가 발생할 수 있음.
✔ 이 오류는 이벤트 루프를 완전히 중지하지 않고 loop.close()를 호출했을 때 발생함.
✔ 따라서 loop.close()를 호출하기 전에 반드시 loop.stop()을 실행하여 루프가 완전히 중지되었는지 확인해야 함.
📌 1. loop.close() 전에 loop.stop()이 실행되지 않으면 발생하는 문제
🔹 loop.call_soon_threadsafe(loop.close)를 호출하더라도, 이벤트 루프가 아직 실행 중이면 RuntimeError가 발생할 수 있음.
🔹 loop.stop()은 이벤트 루프의 실행을 중지하지만, 즉시 종료되지 않고 다음 반복(iteration)에서 중지됨.
🔹 따라서 loop.stop() 호출 직후 바로 loop.close()를 호출하면 여전히 이벤트 루프가 실행 중일 수 있음.
✅ 2. 해결 방법: loop.stop()을 먼저 실행한 후 loop.close()를 안전하게 실행
🔹 올바른 순서:
- loop.call_soon_threadsafe(loop.stop)을 실행하여 이벤트 루프 중지 예약
- time.sleep(0.1)으로 잠시 대기하여 이벤트 루프가 중지될 시간을 줌
- loop.call_soon_threadsafe(loop.close) 실행
import asyncio
import threading
import time
def run_loop(loop):
"""스레드에서 실행할 이벤트 루프"""
asyncio.set_event_loop(loop)
loop.run_forever()
loop = asyncio.new_event_loop()
t = threading.Thread(target=run_loop, args=(loop,), daemon=True)
t.start()
# ✅ 안전한 종료 방법
def shutdown():
print("Stopping loop...")
loop.call_soon_threadsafe(loop.stop) # ✅ 먼저 루프 중지 예약
time.sleep(0.1) # ✅ 루프가 멈출 시간을 확보
loop.call_soon_threadsafe(loop.close) # ✅ 루프 종료
shutdown()
✔ 이제 RuntimeError: Cannot close a running event loop 오류 없이 정상적으로 종료 가능.
✔ 루프가 중지될 시간을 확보하기 위해 time.sleep(0.1)을 추가하여 loop.close() 호출 시 루프가 완전히 종료되었는지 확인.
🚀 결론
✅ loop.call_soon_threadsafe(loop.close)를 호출해도 이벤트 루프가 완전히 중지되지 않으면 RuntimeError가 발생할 수 있음.
✅ loop.stop()을 먼저 호출한 후, 루프가 멈출 시간을 주고(time.sleep(0.1) 또는 asyncio.sleep(0)) loop.close()를 실행해야 함.
✅ 이제 RuntimeError: Cannot close a running event loop 없이 안전하게 이벤트 루프를 종료할 수 있음. 🚀
'Etc > Study' 카테고리의 다른 글
자료구조 [Queue, Stack, Heap] (0) | 2025.03.27 |
---|---|
RAG (Retrieval-Augmented Generation) (0) | 2025.03.27 |
Thread & Task (0) | 2025.03.27 |
Semaphore (0) | 2025.03.27 |