编写一个同时返回普通值的tornado协程
在Tornado中,我们可以使用协程装饰器,把异步函数写得像Python生成器一样整洁。在这里,每次使用yield语句时,函数会把控制权交回给调度器,而最后的raise或return语句则会返回一个值给调用者。但是,有没有办法让这个函数返回一系列的值,同时还可以插入一些异步调用呢?
比如,我想把这个同步函数:
def crawl_site_sync(rooturi):
rootpage = fetch_page_sync(rooturi)
links = extract_links(rootpage)
for link in links:
yield fetch_page_sync(link.uri)
...用这种方式调用:
for page in crawl_site_sync("http://example.com/page.html"):
show_summary(page)
...转换成一个在Tornado中看起来相似的异步函数?例如:
@tornado.gen.coroutine
def crawl_site_async(rooturi):
# Yield a future to the scheduler:
rootpage = yield fetch_page_async(rooturi)
links = extract_links(rootpage)
for link in links:
# Yield a future to the scheduler:
sub_page = yield fetch_page_async(link.uri)
# Yield a value to the caller:
really_really_yield sub_page # ???
那我该怎么调用它呢?
for page in yield crawl_site_sync("http://example.com/page.html"):
# This won't work, the yield won't return until the entire
# coroutine has finished, and it won't give us an iterable.
show_summary(page)
我能想到一些方法来实现这个目标,但这些方法都需要对调用的地方和函数进行很大的修改,这样一来就完全失去了异步版本与同步版本相似的优点,而且也不再能干净地组合在一起。我感觉我一定是漏掉了什么。有没有办法同时使用Python生成器作为一系列懒计算的值和作为Tornado的协程呢?
1 个回答
2
我会使用来自Toro的队列,这个队列是专门为协程设计的,可以让它们像这样协同工作。下面是一个简单的例子:
from tornado.ioloop import IOLoop
from tornado import gen
from tornado.httpclient import AsyncHTTPClient
from toro import Queue
q = Queue(maxsize=1)
@gen.coroutine
def consumer():
item = yield q.get()
while item:
print item
item = yield q.get()
@gen.coroutine
def producer():
try:
client = AsyncHTTPClient()
for url in [
'http://tornadoweb.org',
'http://python.org',
'http://readthedocs.org']:
response = yield client.fetch(url)
item = (url, len(response.body))
yield q.put(item)
# Done.
q.put(None)
except Exception:
IOLoop.current().stop()
raise
future = producer()
IOLoop.current().run_sync(consumer, timeout=20)
关于更详细的网络爬虫示例,可以在Toro的文档中找到,链接在这里:
https://toro.readthedocs.org/en/stable/examples/web_spider_example.html