【问题标题】:Use tornado future to fetch url, two different ways get different results使用tornado future 获取url,两种不同的方式得到不同的结果
【发布时间】:2016-06-01 09:43:01
【问题描述】:

我想使用 tornado 来获取批处理 url。所以我的代码如下所示:

from tornado.concurrent import Future
from tornado.httpclient import AsyncHTTPClient    
from tornado.ioloop import IOLoop    


class BatchHttpClient(object):    
    def __init__(self, urls, timeout=20):    
        self.async_http_client = AsyncHTTPClient()    
        self.urls = urls    
        self.timeout = 20    

    def __mid(self):    
        results = []    
        for url in self.urls:    
            future = Future()    

            def f_callback(f1):    
                future.set_result(f1.result())    

            f = self.async_http_client.fetch(url)    
            f.add_done_callback(f_callback)    
            results.append(future)    
        return results    

    def get_batch(self):    
        results = IOLoop.current().run_sync(self.__mid)    
        return results    


urls = ["http://www.baidu.com?v={}".format(i) for i in range(10)]    
batch_http_client = BatchHttpClient(urls)    
print batch_http_client.get_batch()    

当我运行代码时,出现错误:

ERROR:tornado.application:Exception in callback <function f_callback at 0x7f35458cae60> for <tornado.concurrent.Future object at 0x7f35458c9650>
Traceback (most recent call last):
  File "/usr/local/lib/python2.7/dist-packages/tornado/concurrent.py", line 317, in _set_done
    cb(self)
  File "/home/q/www/base_data_manager/utils/async_util.py", line 21, in f_callback
    future.set_result(f1.result())
  File "/usr/local/lib/python2.7/dist-packages/tornado/concurrent.py", line 271, in set_result
    self._set_done()
  File "/usr/local/lib/python2.7/dist-packages/tornado/concurrent.py", line 315, in _set_done
    for cb in self._callbacks:
TypeError: 'NoneType' object is not iterable

但如果我更改代码如下:

class BatchHttpClient(object):
    def __init__(self, urls, timeout=20):
        self.async_http_client = AsyncHTTPClient()
        self.urls = urls
        self.timeout = 20

    def _get_batch(self, url):
        future = Future()
        f = self.async_http_client.fetch(url)
        def callback(f1):
            print future
            print f1.result()

            future.set_result(f1.result())
            print '---------'
        f.add_done_callback(callback)
        return future

    def __mid(self):
        results = []
        for url in self.urls:
            results.append(self._get_batch(url))
        return results

    def get_batch(self):
        results = IOLoop.current().run_sync(self.__mid)
        return results


urls = ["http://www.baidu.com?v={}".format(i) for i in range(10)]
batch_http_client = BatchHttpClient(urls)
for result in batch_http_client.get_batch():
    print result.body

然后就可以了。 我所做的只是添加一个中间功能,为什么结果不同。

【问题讨论】:

    标签: python tornado future


    【解决方案1】:

    在您的第一个代码 sn-p 中,问题是当您的回调执行时,future 的值是循环设置的最后一个值。换句话说,当它执行时:

    def f_callback(f1):    
        future.set_result(f1.result())    
    

    future 的值始终相同。如果添加print future,您可以看到这一点:对象的地址将始终相同。

    在您的第二个 sn-p 中,每个未来和每个回调都是在循环调用的函数中创建的。因此,每个回调都从一个新的作用域中获取future 的值,从而解决了问题。

    解决此问题的另一种方法是像这样修改__mid

    def __mid(self):
        results = []
        for url in self.urls:
            future = Future()
    
            def make_callback(future):
                def f_callback(f1):
                    future.set_result(f1.result())
                return f_callback
    
            f = self.async_http_client.fetch(url)
            f.add_done_callback(make_callback(future))
            results.append(future)
        return results
    

    通过在make_callback(future) 中创建回调,回调中future 的值来自每个回调的不同范围。

    【讨论】:

    • 天哪,谢谢,它解决了我的问题。我怀疑async_http_client.fetch(url)跑得够快,问题还会存在吗?
    • 我对@9​​87654331@ 的内部结构不够熟悉,无法肯定地说。但是,我不会编写依赖于 async_http_client.fetch(url) 运行“足够快”的代码。那只是自找麻烦。
    • 谢谢。对我很有用。
    【解决方案2】:

    Louis 的回答是正确的,但我想提出一些更简单的替代方案。

    首先,您可以使用 functools.partial 代替 make_callback 包装函数:

    def __mid(self):    
        results = []    
        for url in self.urls:    
            future = Future()    
    
            def f_callback(output, input):    
                output.set_result(f1.result())    
    
            f = self.async_http_client.fetch(url)
            # partial() binds the current value of future to
            # the output argument.
            f.add_done_callback(functools.partial(f_callback, future))
            results.append(future)    
        return results    
    

    但是中间的Future 看起来完全没有必要。这相当于:

    def __mid(self):
        return [self.async_http_client.fetch(url) for url in self.urls]
    

    我个人会让__mid 成为协程:

    @gen.coroutine
    def __mid(self):
        return (yield [self.async_http_client.fetch_url(url) for url in self.urls])
    

    如果您不想使用协程,您可能更愿意将回调传递给AsyncHTTPClient.fetch,而不是对其结果使用Future.add_done_callback

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2022-08-03
      • 1970-01-01
      • 1970-01-01
      • 2019-04-10
      • 1970-01-01
      • 2019-04-14
      • 2011-10-11
      • 2019-08-23
      相关资源
      最近更新 更多