如何打断Tornado协程

时间:2015-04-24 11:01:43

标签: python exception tornado yield 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

通常,函数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)

输出:

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

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

这部分由tornado.gen.WaitIterator解决,但它是错误的(unless I'm mistaken)。但那不是重点。

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

编辑:似乎协同取消是Tornado不支持的东西,不像Python的asyncio,你可以轻松地在每个屈服点抛出CancelledError

3 个答案:

答案 0 :(得分:4)

如果你use WaitIterator according to the instructions,并使用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)

现在,pip install toro获取Event类。 Tornado 4.2将包含Event see the changelog

答案 1 :(得分:1)

从版本5 Set variables开始。

在Python 3上, col1 col2 col3 subID 0 1 11 X 1 1 1 11 X 1 2 1 11 Y 2 3 1 11 Y 2 4 1 11 Z 3 5 2 12 Y 1 6 2 12 Y 1 7 2 12 Z 2 8 2 12 Z 2 9 2 12 X 3 10 3 11 Y 1 11 3 11 X 2 12 3 11 Z 3 13 3 11 Z 3 始终是IOLoop事件循环的包装器,并且使用asyncioasyncio.Future代替它们的Tornado。

因此,您可以使用asyncio.Task取消任务,即Tornado runs on asyncio event loop

您的示例中队列读取while-true循环可能看起来像这样。

asyncio

如果运行它,应该会看到:

import logging
from asyncio import CancelledError

from tornado import ioloop, gen


async def read_off_a_queue():
    while True:
        try:
            await gen.sleep(1)
        except CancelledError:
            logging.debug('Reader cancelled')
            break
        else:
            logging.debug('Pretend a task is consumed')

async def do_some_work():
    await gen.sleep(5)
    logging.debug('do_some_work is raising')
    raise RuntimeError                     

async def main():
    logging.debug('Starting queue reader in background')
    reader_task = gen.convert_yielded(read_off_a_queue())    
    try:
        await do_some_work()
    except RuntimeError:
        logging.debug('do_some_work failed, cancelling reader')
        reader_task.cancel()
        # give the task a chance to clean up, in case it
        # catches CancelledError and awaits something
        try:
            await reader_task            
        except CancelledError:
            pass


if __name__ == '__main__':
    logging.basicConfig(level='DEBUG')        
    ioloop.IOLoop.instance().run_sync(main)

答案 2 :(得分:-1)

警告 :这不是可行的解决方案。看评论。即使您是新手(作为我自己),此示例也可以显示逻辑流程。谢谢@ nathaniel-j-smith和@wgh

使用更原始的东西(例如全局变量)有什么区别?

import asyncio


event = asyncio.Event()
aflag = False


async def short():
    while not aflag:
        print('short repeat')
        await asyncio.sleep(1)
    print('short end')


async def long():
    global aflag

    print('LONG START')
    await asyncio.sleep(3)

    aflag = True
    print('LONG END')


async def main():
    await asyncio.gather(long(), short())

if __name__ == '__main__':
    asyncio.run(main())

它是用于 asyncio 的,但是我想这个想法还是一样的。这是一个半问题(为什么事件会更好?)。然而,解决方案可以产生作者需要的准确结果:

LONG START
short repeat
short repeat
short repeat
LONG END
short end

更新: slides可能对理解问题的核心很有帮助。