Tornado 协程在 Cython 中不起作用

Tornado coroutines don’t work in Cython

此代码在使用 Tornado 4.1 的 Python 3.4.3 中运行 — 它休眠 1 秒然后打印 "Hello World! 123"。但是当使用 Cython 编译时(我试过版本 0.20.1post0 和 0.23dev),它什么都不做。

import tornado.ioloop
import datetime
from tornado import gen

@gen.coroutine
def test():
    yield gen.Task(ioloop.add_timeout, datetime.timedelta(seconds=1))
    return 123

@gen.coroutine
def hello_world():
    print('Hello World! {}'.format((yield test())))

ioloop = tornado.ioloop.IOLoop().instance()
ioloop.run_sync(hello_world)

我用来构建和 运行 Cython 版本的命令:

cython --embed -o hello.c hello.py
gcc -shared -fPIC -O0 -Wall -I/usr/include/python3.4 -o hello.so hello.c
python -c 'import hello'

更新:从 Tornado 4.3 开始原生支持 Cython 协程。以下解决方法仅适用于旧版本的 Tornado。


Cython 目前不支持 Tornado 协程。主要问题是 Cython 编译的生成器没有通过 isinstance(types.GeneratorType)(上次我看没有其他 class 可以代替)。

解决此问题的最佳方法是更改​​ Cython,为生成器添加一个通用基础 class,但作为快速破解,我已经通过此 tornado/gen.py 补丁取得了一些成功:

diff --git a/tornado/gen.py b/tornado/gen.py
index aa931b4..b348f21 100644
--- a/tornado/gen.py
+++ b/tornado/gen.py
@@ -91,6 +91,12 @@ from tornado.concurrent import Future, TracebackFuture
 from tornado.ioloop import IOLoop
 from tornado.stack_context import ExceptionStackContext, wrap

+def _is_generator(obj):
+    # cython generates a new generator type for each module without a
+    # common base class :(
+    return (isinstance(obj, types.GeneratorType) or
+            str(type(obj)) == "<type 'generator'>")
+

 class KeyReuseError(Exception):
     pass
@@ -147,7 +153,7 @@ def engine(func):
             except (Return, StopIteration) as e:
                 result = getattr(e, 'value', None)
             else:
-                if isinstance(result, types.GeneratorType):
+                if _is_generator(result):
                     def final_callback(value):
                         if value is not None:
                             raise ReturnValueIgnoredError(
@@ -219,7 +225,7 @@ def coroutine(func):
                 future.set_exc_info(sys.exc_info())
                 return future
             else:
-                if isinstance(result, types.GeneratorType):
+                if _is_generator(result):
                     def final_callback(value):
                         deactivate()
                         future.set_result(value)