简体   繁体   English

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

[英]gevent to Tornado ioloop - Structure code with coroutines/generators

I'm trying to convert some fairly straightforward gevent code to use the async facilities of Tornado. 我正在尝试转换一些相当简单的gevent代码来使用Tornado的异步工具。 The sample code below uses the ZMQ library to do a very simple request-response. 下面的示例代码使用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()

I can run this as fun() anywhere in my code. 我可以在我的代码中的任何地方运行它作为fun() The .recv() call blocks while waiting for a reply, and the gevent hub can schedule the other parts of the code. .recv()调用在等待回复时阻塞, gevent中心可以调度代码的其他部分。 When values are received, the function returns the value. 收到值时,函数返回值。

I read the problems that can arise with these implicit returns , and I want to run this using the Tornado IOLoop (also because I want to run it within the IPython Notebook). 我阅读了这些隐式返回可能出现问题,我想使用Tornado IOLoop来运行它(也因为我想在IPython Notebook中运行它)。 The following is an option, where recv_future() returns a Future that contains the result: 以下是一个选项,其中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

The problem is that now fun() is not a function, but is a generator. 问题是现在fun()不是一个函数,而是一个生成器。 So if I need to call it from another function, I need to use yield fun() . 因此,如果我需要从另一个函数调用它,我需要使用yield fun() But then the calling function also becomes a generator! 但随后调用函数也变成了生成器!

What is the right way to structure code that uses Python generators? 构建使用Python生成器的代码的正确方法是什么? Do I have to make every function a generator to make it work? 我是否必须使每个功能都成为发电机才能使其工作? What if I need to call one of these functions from __init__() ? 如果我需要从__init__()调用其中一个函数怎么办? Should that also become a generator? 那还应该成为发电机吗?

What if I need to call one of these functions from __init__() ? 如果我需要从__init__()调用其中一个函数怎么办? Should that also become a generator? 那还应该成为发电机吗?

This is one of the currently unsolved issues with explicit asynchronous programming with yield / yield from (on Python 3.3+). 这是目前尚未解决的问题之一,具有yield / yield from显式异步编程(在Python 3.3+上)。 Magic methods don't support them. 魔术方法不支持它们。 You can read some interesting thoughts from a Python core developer on asynchronous programming that touches on this issue here . 你可以阅读从异步编程Python的核心开发者在这个问题上倒是一些有趣的想法在这里

What is the right way to structure code that uses Python generators? 构建使用Python生成器的代码的正确方法是什么? Do I have to make every function a generator to make it work? 我是否必须使每个功能都成为发电机才能使其工作? Not every function, but every function that you want to call a coroutine, and wait for that coroutine to finish before continuing. 不是每个函数,而是每个函数都要调用协程,并等待该协程在继续之前完成。 When you switch to an explicit asynchronous programming model, you generally want to go all-in with it - your entire program runs inside the tornado ioloop. 切换到显式异步编程模型时,通常需要全押 - 整个程序在龙卷风ioloop内部运行。 So, with this toy example, you would just do: 所以,通过这个玩具示例,您可以这样做:

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

It looks like you might be able to get access to the ioloop IPython is using via the IPython.kernel API : 看起来您可以通过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>

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM