如何中断 Tornado 协程

WGH*_*WGH 2 python yield exception tornado coroutine

假设我有两个像这样工作的函数:

@tornado.gen.coroutine
def f():
    for i in range(4):
        print("f", i)
        yield tornado.gen.sleep(0.5)

@tornado.gen.coroutine
def g():
    yield tornado.gen.sleep(1)
    print("Let's raise RuntimeError")
    raise RuntimeError
Run Code Online (Sandbox Code Playgroud)

通常,函数f可能包含无限循环并且永不返回(例如,它可以处理某个队列)。

我想要做的是能够在它产生的任何时候中断它。

最明显的方法不起作用。异常仅在函数f退出后引发(如果它是无穷无尽的,它显然永远不会发生)。

@tornado.gen.coroutine
def main():
    try:
        yield [f(), g()]
    except Exception as e:
        print("Caught", repr(e))

    while True:
        yield tornado.gen.sleep(10)

if __name__ == "__main__":
    tornado.ioloop.IOLoop.instance().run_sync(main)
Run Code Online (Sandbox Code Playgroud)

输出:

f 0
f 1
Let's raise RuntimeError
f 2
f 3
Traceback (most recent call last):
  File "/tmp/test/lib/python3.4/site-packages/tornado/gen.py", line 812, in run
    yielded = self.gen.send(value)
StopIteration

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  <...>
  File "test.py", line 16, in g
    raise RuntimeError
RuntimeError
Run Code Online (Sandbox Code Playgroud)

也就是说,只有当两个协程都返回(两个期货都解决)时才会引发异常。

这部分由 解决tornado.gen.WaitIterator,但它有问题(除非我弄错了)。但这不是重点。

它仍然没有解决中断现有协程的问题。即使启动它的函数退出,协程也会继续运行。

编辑:Tornado 似乎并不真正支持协程取消,这与 Python 的 asyncio 不同,在 Python 的 asyncio 中,您可以轻松地CancelledError在每个屈服点抛出。

A. *_*vis 5

如果您根据说明使用 WaitIterator,并使用toro.Event在协程之间发出信号,它会按预期工作:

from datetime import timedelta
import tornado.gen
import tornado.ioloop

import toro

stop = toro.Event()


@tornado.gen.coroutine
def f():
    for i in range(4):
        print("f", i)

        # wait raises Timeout if not set before the deadline.
        try:
            yield stop.wait(timedelta(seconds=0.5))
            print("f done")
            return
        except toro.Timeout:
            print("f continuing")


@tornado.gen.coroutine
def g():
    yield tornado.gen.sleep(1)
    print("Let's raise RuntimeError")
    raise RuntimeError


@tornado.gen.coroutine
def main():
    wait_iterator = tornado.gen.WaitIterator(f(), g())
    while not wait_iterator.done():
        try:
            result = yield wait_iterator.next()
        except Exception as e:
            print("Error {} from {}".format(e, wait_iterator.current_future))
            stop.set()
        else:
            print("Result {} received from {} at {}".format(
                result, wait_iterator.current_future,
                wait_iterator.current_index))


if __name__ == "__main__":
    tornado.ioloop.IOLoop.instance().run_sync(main)
Run Code Online (Sandbox Code Playgroud)

现在,pip install toro获取 Event 类。Tornado 4.2 将包含事件,请参阅更改日志。