Tornado 의 coroutine
Tornado 에서 asychronous 한 code 를 짤 때 coroutine 을 이용하라고 권한다. [ref. 1]그래서 사용방법을 대략적으로 알아보자.
사용방법
@gen.coroutine 은 tornado 에서 callback 을 사용하는 code 를 yield 를 사용하는 모습으로 변경시켜주는 wrapper 정도로 생각하면 될 듯 하다.@gen.coroutine 를 사용하면 아래의 code 를
class AsyncHandler(RequestHandler): @asynchronous def get(self): http_client = AsyncHTTPClient() http_client.fetch("http://example.com", callback=self.on_fetch) def on_fetch(self, response): do_something_with_response(response) self.render("template.html")
이렇게 바꿀 수 있다.
from tornado import gen @gen.coroutine def fetch_coroutine(url): http_client = AsyncHTTPClient() response = yield http_client.fetch(url) # In Python versions prior to 3.3, returning a value from # a generator is not allowed and you must use # raise gen.Return(response.body) # instead. return response.body
개인적으로는 yield 가 더 혼란스럽긴 한데, 여튼 대략적으로 설명하면, 처음에 get() 이 호출되고, fetch 를 하면 fetch 는 network 작업이기 때문에 response 가 올 때까지 local 은 할일이 없다. 그러기 때문에 이 시간동안에 다른 일을 하고, 끝나면, callback 인 self.on_fetch() 를 호출하면 된다. 이 때 다른일이라 함은 get() 의 호출이 끝난 후 다음 code 가 된다.
그런데 yield 를 사용하게 되면, callback 부분이 get() 안으로 들어온다. 의미자체는 get() 이 호출될 때 할 일들을 한번에 적어놓는다는 의미에서 나름 명확한 면은 있지만, "source 에서 fetch() 가 끝나고 yield 를 해준 후의 흐름"을 파악하기가 애매한 부분이 있다.
gen.coroutine source
coroutine 을 사용할 때는 아래와 같은 흐름을 갖는다.- result = func(*args, **kwargs) --> fetch_coroutine
- yield --> result = func(*args, **kwargs) 로 돌아온다.
- yielded = next(result)
- Runner(result, future, yielded)
- Runner.run() 에서 일을 마치면, send() 를 해준다.
def coroutine(func, replace_callback=True): return _make_coroutine_wrapper(func, replace_callback=True) def _make_coroutine_wrapper(func, replace_callback): @functools.wraps(func) def wrapper(*args, **kwargs): future = TracebackFuture() ... try: result = func(*args, **kwargs) except (Return, StopIteration) as e: result = _value_from_stopiteration(e) except Exception: future.set_exc_info(sys.exc_info()) return future else: if isinstance(result, GeneratorType): ... try: orig_stack_contexts = stack_context._state.contexts yielded = next(result) if stack_context._state.contexts is not orig_stack_contexts: yielded = TracebackFuture() yielded.set_exception( stack_context.StackContextInconsistentError( 'stack_context inconsistency (probably caused ' 'by yield within a "with StackContext" block)')) except (StopIteration, Return) as e: future.set_result(_value_from_stopiteration(e)) except Exception: future.set_exc_info(sys.exc_info()) else: Runner(result, future, yielded) try: return future finally: future = None class Runner(object): def __init__(self, gen, result_future, first_yielded): self.gen = gen self.result_future = result_future self.future = _null_future self.yield_point = None self.pending_callbacks = None self.results = None self.running = False self.finished = False self.had_exception = False self.io_loop = IOLoop.current() # For efficiency, we do not create a stack context until we # reach a YieldPoint (stack contexts are required for the historical # semantics of YieldPoints, but not for Futures). When we have # done so, this field will be set and must be called at the end # of the coroutine. self.stack_context_deactivate = None if self.handle_yield(first_yielded): self.run() def run(self): if self.running or self.finished: return try: self.running = True while True: future = self.future if not future.done(): return self.future = None try: ... try: value = future.result() except Exception: self.had_exception = True exc_info = sys.exc_info() if exc_info is not None: yielded = self.gen.throw(*exc_info) exc_info = None else: yielded = self.gen.send(value)
See Also
References
- Coroutines — Tornado 4.4.2 documentation
- tornado.gen — Simplify asynchronous code — Tornado 4.4.2 documentation
댓글 없음:
댓글 쓰기