Going by the tornado.gen documentation can someone help me understand the exact difference between tornado.gen.coroutine and tornado.gen.engine
Coroutines use the Python await keyword to suspend and resume execution instead of a chain of callbacks (cooperative lightweight threads as seen in frameworks like gevent are sometimes called coroutines as well, but in Tornado all coroutines use explicit context switches and are called as asynchronous functions).
Originally named the Tornado GR1 the aircraft's first use in live operations was during the Gulf War in 1991, when 60 Tornado GR1s were deployed from bases in Saudi Arabia and Bahrain. Later they were upgraded to the GR4 model, which has been used ever since over the skies of Kosovo, Afghanistan, Iraq and Syria.
As the tornado documentation for gen.engine
says:
This decorator is similar to coroutine, except it does not return a Future and the callback argument is not treated specially.
And as the gen.coroutine
documentation says
From the caller’s perspective, @gen.coroutine is similar to the combination of @return_future and @gen.engine.
gen.engine
is basically an older, less streamlined version of what coroutine does. If you're writing new code, you should follow the documentation's advice and always use tornado.gen.coroutine
.
It's pretty evident if you look at the code for both functions (with documentation stripped out).
engine:
def engine(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
runner = None
def handle_exception(typ, value, tb):
if runner is not None:
return runner.handle_exception(typ, value, tb)
return False
with ExceptionStackContext(handle_exception) as deactivate:
try:
result = func(*args, **kwargs)
except (Return, StopIteration) as e:
result = getattr(e, 'value', None)
else:
if isinstance(result, types.GeneratorType):
def final_callback(value):
if value is not None:
raise ReturnValueIgnoredError(
"@gen.engine functions cannot return values: "
"%r" % (value,))
assert value is None
deactivate()
runner = Runner(result, final_callback)
runner.run()
return
if result is not None:
raise ReturnValueIgnoredError(
"@gen.engine functions cannot return values: %r" %
(result,))
deactivate()
# no yield, so we're done
return wrapper
coroutine:
def coroutine(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
runner = None
future = TracebackFuture()
if 'callback' in kwargs:
callback = kwargs.pop('callback')
IOLoop.current().add_future(
future, lambda future: callback(future.result()))
def handle_exception(typ, value, tb):
try:
if runner is not None and runner.handle_exception(typ, value, tb):
return True
except Exception:
typ, value, tb = sys.exc_info()
future.set_exc_info((typ, value, tb))
return True
with ExceptionStackContext(handle_exception) as deactivate:
try:
result = func(*args, **kwargs)
except (Return, StopIteration) as e:
result = getattr(e, 'value', None)
except Exception:
deactivate()
future.set_exc_info(sys.exc_info())
return future
else:
if isinstance(result, types.GeneratorType):
def final_callback(value):
deactivate()
future.set_result(value)
runner = Runner(result, final_callback)
runner.run()
return future
deactivate()
future.set_result(result)
return future
return wrapper
Both of these are probably pretty hard to understand at first glance. But still, it's obvious that the code is very similar, except that @gen.coroutine
has some special handling of the callback
kwarg, and it builds/returns a Future
. @gen.engine
has some code that specifically throws an error if you try to return something from it, rather than putting it in the Future
.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With