개요
Tornado로 개발한 애플리케이션에서 Redis를 비동기로 연결하여 사용하고 있다.
from tornado.web import RequestHandler
import redis.asyncio as redis
class BaseHandler(RequestHandler):
def prepare(self):
self.redis_connection = redis.Redis()
def on_finish(self):
self.redis_connection.close()
그러나 테스트를 해보니 Redis의 연결을 종료하는 시점에 다음과 같은 에러가 발생하는 것을 발견했다.
/home/brian/repos/medigator/venv/lib/python3.8/site-packages/aioredis/client.py:1047: RuntimeWarning: coroutine 'Redis.close' was never awaited
확인해보니 redis.asyncio의 모든 함수는 코루틴이라 동기 함수를 호출하는 방식으로는 사용할 수 없어서 발생한 에러였다. 따라서 redis.close를 아래와 같이 asyncio.run 함수로 전달하여 수행하려고 했는데……
import asyncio
class BaseHandler(RequestHandler):
def on_finish(self):
asyncio.run(self.redis_connection.close())
이번에는 런타임에러가 발생했다.
RuntimeError: asyncio.run() cannot be called from a running event loop
찾아보니 asyncio.run 함수는 같은 스레드에서 다른 asyncio 이벤트 루프가 실행 중일 때는 사용할 수 없다고 한다.
처리할 방법을 찾아보다가 on_finish에서 동작해야 하는 코드를 비동기로 실행할 수 있는 방법이 없을까 고민하기 시작했다.
방법을 적어둔다.
on_finish는 비동기화 가능한가?
결론부터 말하자면 아니다.
Tornado RequestHandler는 비동기화할 수 있는 여러 함수를 제공하고 있는데, on_finish는 해당되지 않는다. 즉, async 키워드를 사용할 수 없다.
처리 방법
on_finish 함수 자체를 비동기로 생성할 수는 없지만, IOLoop.current().add_callback 함수를 사용하여 비동기로 코드를 실행하도록 할 수 있다.
비동기로 실행해야 하는 별도의 함수를 정의하고, on_finish 함수 내에서 IOLoop.current().add_callback로 수행하는 방법이다.
from tornado.ioloop import IOLoop
class BaseHandler(RequestHandler):
async def on_finish_async(self):
await self.redis_connection.close()
def on_finish(self):
IOLoop.current().add_callback(self.on_finish_async)
참고 문서
https://stackoverflow.com/questions/51375536/tornado-make-on-finish-asynchronous
asyncio.run() cannot be called from a running event loop 에러 질문 드립니다
https://www.tornadoweb.org/en/stable/ioloop.html#tornado.ioloop.IOLoop.add_callback