gevent to Tornado ioloop - 结构代码与协同程序/生成器

时间:2014-08-25 20:24:31

标签: python asynchronous tornado gevent

我试图转换一些相当简单的gevent代码来使用Tornado的异步设施。下面的示例代码使用ZMQ库来执行非常简单的请求响应。

import zmq.green as zmq

def fun():
    i = zmq.Context.instance()
    sock = i.socket(zmq.REQ)
    sock.connect('tcp://localhost:9005')
    sock.send('Ping')
    return sock.recv()

我可以在代码中的任何位置以fun()运行它。 .recv()调用在等待回复时阻塞,gevent集线器可以调度代码的其他部分。收到值时,函数返回值。

我读了problems that can arise with these implicit returns,我想使用Tornado IOLoop运行它(也因为我想在IPython Notebook中运行它)。以下是一个选项,其中recv_future()返回包含结果的Future

@gen.coroutine
def fun():
    i = zmq.Context.instance()
    sock = i.socket(zmq.REQ)
    sock.connect('tcp://localhost:9005')
    sock.send('Ping')
    msg = yield recv_future(sock)
    print "Received {}".format(msg[0])
    raise gen.Return(msg)

def recv_future(socket):
    zmqstream = ZMQStream(socket)  # Required for ZMQ
    future = Future()
    def _finish(reply):
        future.set_result(reply)
    zmqstream.on_recv(_finish)
    return future

问题是现在fun()不是一个函数,而是一个生成器。因此,如果我需要从另一个函数调用它,我需要使用yield fun()。但随后调用函数也变成了生成器!

构建使用Python生成器的代码的正确方法是什么?我是否必须使每个功能都成为发电机才能使其工作?如果我需要从__init__()调用其中一个函数怎么办?那还应该成为一个发电机吗?

1 个答案:

答案 0 :(得分:1)

  

如果我需要从__init__()调用其中一个函数怎么办?应该   那也成了发电机?

这是使用yield / yield from进行显式异步编程的当前未解决的问题之一(在Python 3.3+上)。魔术方法不支持它们。您可以从Python核心开发人员那里了解有关此问题here的异步编程的一些有趣想法。

  

构建使用Python生成器的代码的正确方法是什么?   我是否必须使每个功能都成为发电机才能使其工作?   不是每个函数,而是每个函数都要调用协程,并等待该协程在继续之前完成。切换到显式异步编程模型时,通常需要全押 - 整个程序在龙卷风ioloop内部运行。所以,通过这个玩具示例,您可以这样做:

from tornado.ioloop import IOLoop
from tornado.gen import coroutine
from tornado.concurrent import Future

@gen.coroutine
def fun():
    i = zmq.Context.instance()
    sock = i.socket(zmq.REQ)
    sock.connect('tcp://localhost:9005')
    sock.send('Ping')
    msg = yield recv_future(sock)
    print "Received {}".format(msg[0])
    raise gen.Return(msg)

def recv_future(socket):
    zmqstream = ZMQStream(socket)  # Required for ZMQ
    future = Future()
    def _finish(reply):
        future.set_result(reply)
    zmqstream.on_recv(_finish)
    return future

if __name__ == "__main__":
    ioloop = IOLoop.instance()
    ioloop.add_callback(fun)
    ioloop.start() # This will run fun, and then block forever.
    #ioloop.run_sync(fun) # This will start the ioloop, run fun, then stop the ioloop

看起来您可以通过IPython.kernel API访问ioloop IPython:

In [4]: from IPython.kernel.ioloop import manager

In [5]: manager.ioloop.IOLoop.instance()
Out[5]: <zmq.eventloop.ioloop.ZMQIOLoop at 0x4249ac8>