zoukankan      html  css  js  c++  java
  • tornado用户指引(三)------tornado协程使用和原理(二)

    Python3.5  async和await

    async和await是python3.5引入的2个新的关键字(用这两个关键字编写的函数也称之为"原生协程").

    从tornado4.3开始,你可以在使用yield的tornado协程中使用这两个关键字。只需将原来用@gen.coroutine装饰的函数定义成async def func(),并将原来yield语句改为await即可。

    本文的后面部分为了和老版本的python兼容将会继续使用yield关键字,但是使用async和await将会更快。

    举例:

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

    async def fetch_coroutine():
        http_cli = AsyncHTTPClient()
        reponse = await http_cli.fetch("http://www.baidu.com")
        print(reponse.body)

    IOLoop.instance().run_sync(fetch_coroutine)
    yield关键字比await关键字更加通用。比如,在基于yield的协程中你可以yield一个Future的列表,但是在原生的协程中要想这么做,你必须用tornado.gen.multi来装饰Future的列表。
    另外,你可以使用tornado.gen.convert_yielded来装饰任何可以用yield关键字工作的对象,这样你就可以在await中使用它们。
    尽管原生的协程不与任何特定的框架绑定(比如tornado框架,原生协程也不需要额外的tornado.gen.coroutine或者asyncio.coroutine装饰器来装饰).但是不同实现的协程之间是不兼容的。
    当第一个协程被调用时会选择一个协程的执行体,然后这个执行体会被通过await调用的其它协程所共享。
    tornado的协程执行体被设计的十分通用,它可以接受其它任何框架的await的对象。
    其它的协程实现可能有局限性(比如asyncio协程的执行体不能接受其它框架的协程)。
    因此,我们建议使用tornado的协程执行体,这样你就可以在你的应用中使用不同框架的协程。
    如果你已经使用了asyncio执行体来执行协程,这时你可以通过tornado.platform.asyncio.to_asyncio_future将
    tornado的协程适配成可以在asyncio中执行的协程。
    举例:
    下面的例子演示了如何asyncio中执行tornado的协程:
    注意:执行ASyncIOMainLoop().install是为了初始化ioLoop为asyncio的ioLoop.
    from tornado import gen
    from tornado.platform.asyncio import to_asyncio_future,AsyncIOMainLoop
    from tornado.httpclient import AsyncHTTPClient
    import asyncio

    @gen.coroutine
    def tornado_coroutine():
        cli = AsyncHTTPClient()
        response = yield cli.fetch("http://www.baidu.com")
        print(response.body)

    AsyncIOMainLoop().install()
    asyncio.get_event_loop().run_until_complete(to_asyncio_future(tornado_coroutine()))

    tornado协程的工作原理
    包含yield语句的函数是一个生成器。所有的生成器都是异步的。当我们调用生成器函数的时候,生成器函数返回一个生成器对象,而不是像普通函数那样直接执行完成。@gen.coroutine装饰器通过yield表达式来和生成器对象交互,调用@gen.coroutine装饰的函数会返回一个Future.
    下面是一个coroutine装饰器内部循环的简化实现:
    可以看到,coroutine装饰器内部有一个Runner对象,这个对象将我们的生成器函数包装为self.gen.
    这个对象就是协程的运行体,它会在内部一直循环运行生成器,直到生成器函数阻塞返回一个Future或者结束。如果遇到yield返回的Future,便通过Future对象的结束回调函数来继续运行生成器函数。
    具体实现是:
    1.当生成器函数运行到yield时,会得到一个Future.为这个Future添加结束回调通知函数callback.执行体释放执行权给ioLoop继续执行其它协程。
    2.当这个Future异步运行结束时,会调用callback函数。callback函数得到Future的运行结果,并将结果通过生成器的send方法发送给生成器。这样生成器函数就会在yield的地方返回Future的异步执行结果并继续运行。
    3.1接着生成器函数继续运行,直到生成器函数运行结束
    3.2或者遇到下一个yield转到步骤1
    # Simplified inner loop of tornado.gen.Runner
    def run(self):
    # send(x) makes the current yield return x.
    # It returns when the next yield is reached
        future = self.gen.send(self.next)
        def callback(f):
            self.next = f.result()
            self.run()
        future.add_done_callback(callback)

    如何调用一个协程
    协程并不以一般的方式产生异常。协程中产生的任何异常将会被Future包装起来直到它被yielded.
    下面代码展示了我们函数产生的异常是如何被包装到future中的:
    func为我们的函数。
    try:
        result = func(*args, **kwargs)
    except (Return, StopIteration) as e:
        result = _value_from_stopiteration(e)
    except Exception:
        future.set_exc_info(sys.exc_info())
    return future
    这意味着我们必须以正确的方式调用协程,否则你可能会忽略一些发生的错误。
    在大部分情况下,任何调用协程的函数本身也需要是一个协程,而且需要在调用另外一个协程的地方使用yield关键字。当你需要重写父类中的函数时,你需要翻阅相应的文档以确定函数是否允许被实现为一个协程,文档中需要说明函数需要是一个协程或者需要返回一个Future.
    下面的函数修正了上面的错误,在一个协程中通过yield关键字调用divide这个协程。
    @gen.coroutine
    def good_call():
        # yield will unwrap the Future returned by divide() and raise
        # the exception.
        yield divide(1, 0)
    有时候你仅仅是想执行一个协程并不关心其结果,这种情况建议你使用IOLoop.spawn_callback(callback, *args, **kwargs)。这样如果协程执行失败,ioLoop会将调用栈记录到log中。
    # The IOLoop will catch the exception and print a stack trace in
    # the logs. Note that this doesn't look like a normal call, since
    # we pass the function object to be called by the IOLoop.
    IOLoop.current().spawn_callback(divide, 1, 0)


    最后,如果在程序顶层,ioloop还没有运行,你可以通过run_sync方法开始ioloop的运行,并执行协程。run_sync常常用来执行main协程,main里面包含了一系列的协程。

    # run_sync() doesn't take arguments, so we must wrap the
    # call in a lambda.
    IOLoop.current().run_sync(lambda: divide(1, 0))
    注意:由于run_sync只接受一个函数参数,所以你需要通过lambda表达式传递函数参数,或者通过functools.partial来将函数变为偏函数。
    IOLoop.current().run_sync(functools.partial(divide,1, 0))
    ---------------------
    作者:self-motivation
    来源:CSDN
    原文:https://blog.csdn.net/happyanger6/article/details/51277407
    版权声明:本文为博主原创文章,转载请附上博文链接!

  • 相关阅读:
    PyCharm设置中文字体
    pycharm中设置鼠标滚动放大和缩小页面
    cas5.3.2单点登录-自定义登录页面(十四)
    P1616疯狂的采药
    P2430严酷的训练
    P1164小A点菜
    P1015回文数
    P2871 手链
    《学习OpenCV》课后习题解答6
    《学习OpenCV》课后习题解答5
  • 原文地址:https://www.cnblogs.com/b02330224/p/10213776.html
Copyright © 2011-2022 走看看