tornado源码分析(四)之future、gen.coroutine

future是什么

在事件驱动编程模型中,会有很多的事件循环,各事件循环在创建异步事件时可以同时创建一个future对象,并将创建的异步事件与该future对象存储在一起,并将所有传入的callback回掉函数存入future中,当异步事件发生后,直接调用future的set_result函数,该函数会调用所有存在future中的回掉函数。

所以future就代表某个时间循环的某个异步事件,它让我们可以动态的向异步事件添加回掉函数。

举例说明:

from tornado.tcpserver import TCPServer
from tornado import gen
from tornado.concurrent import Future


class MyTCPConnection(object):
    def __init__(self, stream, address, server):
        self.stream = stream
        self.address = address
        self.server = server

    def start_serving(self, future=None):
        future = self.stream.read_until("that is all!".encode()) #在iostream中创建了一个异步读事件
        future.add_done_callback(self.message_recived) #在将message_recived作为该事件的回掉函数

    def message_recived(self, future):
        message = future.result().decode()

        if message == "that is all!": #如果仅发送that is all!,则说明客户端消息已发送完毕
            print("no more messages")
            self.stream.close()
            self.server.on_conn_close(self)
            self.server = None
        else:
            print("data recieved: ", message)
            future = self.stream.write(message.encode()) #在iostream中创建了一个异步写事件
            future.add_done_callback(self.start_serving) #时间出发说明写已完毕,所以回掉即为再次读消息的函数



class MyTCPServer(TCPServer):
    def __init__(self):
        super().__init__()
        self._conns = set()

    #tcpserver“连接已建立事件”的回掉函数
    def handle_stream(self, stream, address):
        conn = MyTCPConnection(stream, address, self)
        self._conns.add(conn)
        return conn.start_serving()

    def on_conn_close(self, conn):
        self._conns.remove(conn)


if __name__ == "__main__":
    server = MyTCPServer()
    server.listen(8000, '0.0.0.0')
    from tornado.ioloop import IOLoop
    IOLoop().current().start()

在上述例子中,我们在start_serving中创建了一个从客户端都消息的异步事件,istream给我们返回了代表该异步事件的future对象,我们在这个future对象上添加了一个回掉函数message_recived,在该函数中从future中取出数据,并做相应处理。之后会创建一个向客户端写数据的异步事件,同样的istream给我们返回了代表该异步事件的future,我们又将start_serving作为该事件的回掉函数添加至future,从而实现了异步的循环读写的功能。

gen.coroutine——以同步的方式编写异步程序

上面的里子只是实现一个简单的功能,但是程序写的却非常的绕,让人看起来很蛋疼,如果功能再复杂一点我们会直接蛋碎的。gen.coroutine的作用就是帮我们实现那些调过来,调过去的工作,我们只需要以同步的方式写我们的代码,只是在遇到异步事件时将异步事件(future)yield出去就行了,gen.coroutine帮我们处理这些异步事件,在异步事件结束后再调度我们的协程。

用gen.coroutine实现上述例子:

from tornado.tcpserver import TCPServer
from tornado import gen


class MyTCPConnection(object):
    def __init__(self, stream, address):
        self.stream = stream
        self.address = address

    @gen.coroutine
    def start_serving(self, server):
        while True:
            #循环从stream中读取消息,每个消息以that is all!结尾
            future = self.stream.read_until("that is all!".encode())
            message = yield future #将异步事件yield出去,gen.coroutine会处理该异步事件,并在结束后将结果send给我们
            message = message.decode()

            if message == "that is all!": #如果仅发送that is all!,则说明客户端消息已发送完毕
                print("no more messages")
                self.stream.close()
                server.on_conn_close(self)
                break
            else:
                print("data recieved: ", message)
                future = self.stream.write(message.encode()) #把接收到的消息以异步的方式写回至client
                yield future #将异步事件yield出去,gen.coroutine会处理该异步事件,并在结束后将结果send给我们


class MyTCPServer(TCPServer):
    def __init__(self):
        super().__init__()
        self._conns = set()

    #tcpserver“连接已建立事件”的回掉函数
    def handle_stream(self, stream, address):
        conn = MyTCPConnection(stream, address)
        self._conns.add(conn)
        return conn.start_serving(self)

    def on_conn_close(self, conn):
        self._conns.remove(conn)


if __name__ == "__main__":
    server = MyTCPServer()
    server.listen(8000, '0.0.0.0')
    from tornado.ioloop import IOLoop
    IOLoop().current().start()

使用gen.coroutine后,程序的逻辑变得极为清晰,就是循环的从socket中读消息,然后把消息写回!

gen.coroutine实现的原理

python yield用法:略

主要由三部分完成:coroutine,Runner.run,Runner.handle_yeild

coroutine:装饰器,负责为用户函数添加额外的动作。1、将用户函数整体看做一个异步事件,创建一个future代表该异步事件。2、执行用户函数,获得generator对象。3、执行next函数,获得用户函数中yield出的第一个对象。5、创建Runner对象,并建立runner与future的循环引用。6、返回future(需要保证该future保存在某处,否则会导致runner与future均被释放,用户函数无法执行)。

Runner.handle_yeild:处理用户函数中yield出来的对象。

1、yeild出一个future,为future设置回掉函数,在回掉函数中调用Runner.run。

2、yield出来一个list或dict,且至少包含一个future,则创建一个multi_future(所有子future结束后,该future结束),并设置回掉函数,在回掉函数中调用Runner.run。

3、yeild出一个yield_point,这是老版本中的异步事件,新版本已用future代替。

4、yield出的对象不是也不包含future,则报错(如果不知道自己要yield对象是否是future,可以用gen.maby_future包装)。

Runner.run:用户yield出来的future执行完毕后,将future的结果send进用户函数,有两种情况。

1、用户函数未结束,yield出新的对象,调用Runner.handle_yeild处理新获得的yield出来的对象。

2、用户函数结束,产生StopIteration异常,捕获该异常,将函数的结果设置为整个coroutine中创建的代表用户函数的future的result。

 

你可能感兴趣的:(tornado)