[英]How to interrupt 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
通常,函数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
解决,但它有问题(除非我弄错了)。 但这不是重点。
它仍然没有解决中断现有协程的问题。 即使启动它的函数退出,协程也会继续运行。
编辑:Tornado 似乎并不真正支持协程取消,这与 Python 的 asyncio 不同,在 Python 的 asyncio 中,您可以在每个屈服点轻松抛出CancelledError
。
如果您根据说明使用 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)
现在, pip install toro
来获取 Event 类。 Tornado 4.2 将包含事件, 请参阅更改日志。
从版本 5 开始, Tornado 在asyncio
事件循环上运行。
在 Python 3 上,
IOLoop
始终是asyncio
事件循环的包装器,并且使用asyncio.Future
和asyncio.Task
代替它们的 Tornado 对应项。
因此您可以使用asyncio
任务取消,即asyncio.Task.cancel
。
您的队列读取 while-true 循环示例可能如下所示。
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)
如果你运行它,你应该看到:
DEBUG:asyncio:Using selector: EpollSelector
DEBUG:root:Starting queue reader in background
DEBUG:root:Pretend a task is consumed
DEBUG:root:Pretend a task is consumed
DEBUG:root:Pretend a task is consumed
DEBUG:root:Pretend a task is consumed
DEBUG:root:do_some_work is raising
DEBUG:root:do_some_work failed, cancelling reader
DEBUG:root:Reader cancelled
警告:这不是一个有效的解决方案。 看评论。 尽管如此,如果您是新手(就像我自己),这个例子可以显示逻辑流程。 谢谢@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 ,但我想这个想法保持不变。 这是一个半问题(为什么Event会更好?)。 然而,解决方案产生了作者需要的确切结果:
LONG START
short repeat
short repeat
short repeat
LONG END
short end
更新:这张幻灯片可能对理解问题的核心很有帮助。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.