编写一个同时返回普通值的tornado协程

4 投票
1 回答
1075 浏览
提问于 2025-04-17 22:19

在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

撰写回答