Python异步运行事件循环一次吗?

问题描述

我正在尝试理解异步库,特别是使用套接字。我写了一些代码,试图获得理解,

我想异步运行发送器套接字和接收器套接字。我把所有的数据都发送到了最后一个,但是之后我必须再运行一次循环。看一下如何做到这一点,我发现了this link from stackoverflow,我在下面实现了它--但是这里发生了什么呢?有没有比先调用stop再调用run_forever更好/更明智的方法呢?

事件循环中stop()的文档为:

停止运行事件循环。

在调用stop()之前计划的每个回调都将运行。在调用stop()之后计划的回调将不会运行。但是,如果稍后再次调用run_ever(),则将运行这些回调。

run_forever()的文档为:

运行到调用Stop()为止。

问题:

  • 为什么run_forever是通向run_once的唯一途径?这根本没有意义
  • 有没有更好的方法?
  • 我的代码看起来像是使用异步库进行编程的合理方式吗?
  • 除了asyncio.async()之外,是否还有更好的方法将任务添加到事件循环?loop.create_task在我的Linux系统上显示错误。

https://gist.github.com/cloudformdesign/b30e0860497f19bd6596


解决方案

stop(); run_forever()技巧的工作方式取决于stop的实现方式:

def stop(self):
    """Stop running the event loop.

    Every callback scheduled before stop() is called will run.
    Callback scheduled after stop() is called won't.  However,
    those callbacks will run if run() is called again later.
    """
    self.call_soon(_raise_stop_error)

def _raise_stop_error(*args):
    raise _StopError

因此,下次事件循环运行并执行挂起的回调时,它将调用_raise_stop_error,从而引发_StopErrorrun_forever循环将仅在出现特定异常时中断:

def run_forever(self):
    """Run until stop() is called."""
    if self._running:
        raise RuntimeError('Event loop is running.')
    self._running = True
    try:
        while True:
            try:
                self._run_once()
            except _StopError:
                break
    finally:
        self._running = False

因此,通过调度stop()然后调用run_forever,您最终运行了事件循环的一次迭代,然后一旦遇到_raise_stop_error回调就停止。您可能还注意到,_run_once是由run_forever定义和调用的。您可以直接调用它,但是如果没有任何准备好运行的回调,那么有时可以使用挡路,这可能不是您想要的。我认为目前没有更干净的方法来做这件事-答案是由asyncio撰稿人Andrew Svetlov提供的;他可能知道是否有更好的选择。:)

通常,您的代码看起来很合理,尽管我认为您一开始就不应该使用这种run_once方法。它不是确定性的;如果您有一个较长的列表或较慢的系统,可能需要两次以上的额外迭代才能打印所有内容。相反,您应该只发送一个通知接收器关闭的哨兵,然后等待发送和接收协同例程完成:

import sys
import time
import socket
import asyncio


addr = ('127.0.0.1', 1064)
SENTINEL = b"_DONE_" 

# ... (This stuff is the same)

@asyncio.coroutine
def sending(addr, dataiter):
    loop = asyncio.get_event_loop()
    for d in dataiter:
        print("Sending:", d)
        sock = socket.socket()
        yield from send_close(loop, sock, addr, str(d).encode())
    # Send a sentinel
    sock = socket.socket()
    yield from send_close(loop, sock, addr, SENTINEL)

@asyncio.coroutine
def receiving(addr):
    loop = asyncio.get_event_loop()
    sock = socket.socket()
    try:
        sock.setblocking(False)
        sock.bind(addr)
        sock.listen(5)

        while True:
            data = yield from accept_recv(loop, sock)
            if data == SENTINEL:  # Got a sentinel
                return
            print("Recevied:", data)
    finally: sock.close()

def main():
    loop = asyncio.get_event_loop()
    # add these items to the event loop
    recv = asyncio.async(receiving(addr), loop=loop)
    send = asyncio.async(sending(addr, range(10)), loop=loop)
    loop.run_until_complete(asyncio.wait([recv, send]))

main()
最后,asyncio.async是向事件循环添加任务的正确方式。create_task是在Python 3.4.2中添加的,所以如果您有更早的版本,它将不存在。

相关文章