使用Python 3的异步I / O

在本教程中,您将绕过Python 3.4中引入的异步I / O设施,并在Python 3.5和3.6中进行了进一步改进。

Python以前很少有异步编程的绝佳选择。 新的异步I / O支持最终带来了一流的支持,包括高级API和标准支持,旨在统一多个第三方解决方案(Twisted,Gevent,Tornado,asyncore等)。

重要的是要了解,由于快速的迭代,范围以及为现有的异步框架提供迁移路径的需要,学习Python的异步IO并非易事。 我将重点介绍最新和最精简的内容。

有许多运动部件以有趣的方式在线程边界,进程边界和远程机器之间进行交互。 有特定于平台的差异和限制。 让我们跳进去。

可插拔事件循环

异步IO的核心概念是事件循环。 在一个程序中,可能会有多个事件循环。 每个线程最多具有一个活动事件循环。 事件循环提供以下功能:

  • 注册,执行和取消延迟的呼叫(带有超时)。
  • 创建用于各种通信的客户端和服务器传输。
  • 启动子流程和相关的传输,以便与外部程序进行通信。
  • 将昂贵的函数调用委派给线程池。

快速范例

这是一个小示例,该示例启动两个协程并延迟调用一个函数。 它显示了如何使用事件循环为程序提供动力:

import asyncio


async def foo(delay):
    for i in range(10):
        print(i)
        await asyncio.sleep(delay)


def stopper(loop):
    loop.stop()


loop = asyncio.get_event_loop()

# Schedule a call to foo()
loop.create_task(foo(0.5))
loop.create_task(foo(1))
loop.call_later(12, stopper, loop)

# Block until loop.stop() is called()
loop.run_forever()
loop.close()

AbstractEventLoop类提供事件循环的基本协定。 事件循环需要支持很多事情:

  • 调度函数和协程以执行
  • 创造未来和任务
  • 管理TCP服务器
  • 处理信号(在Unix上)
  • 处理管道和子流程

以下是与运行和停止事件以及调度函数和协程相关的方法:

class AbstractEventLoop:
    """Abstract event loop."""

    # Running and stopping the event loop.

    def run_forever(self):
        """Run the event loop until stop() is called."""
        raise NotImplementedError

    def run_until_complete(self, future):
        """Run the event loop until a Future is done.

        Return the Future's result, or raise its exception.
        """
        raise NotImplementedError

    def stop(self):
        """Stop the event loop as soon as reasonable.

        Exactly how soon that is may depend on the implementation, but
        no more I/O callbacks should be scheduled.
        """
        raise NotImplementedError

    def is_running(self):
        """Return whether the event loop is currently running."""
        raise NotImplementedError

    def is_closed(self):
        """Returns True if the event loop was closed."""
        raise NotImplementedError

    def close(self):
        """Close the loop.

        The loop should not be running.

        This is idempotent and irreversible.

        No other methods should be called after this one.
        """
        raise NotImplementedError

    def shutdown_asyncgens(self):
        """Shutdown all active asynchronous generators."""
        raise NotImplementedError

    # Methods scheduling callbacks.  All these return Handles.

    def _timer_handle_cancelled(self, handle):
        """Notification that a TimerHandle has been cancelled."""
        raise NotImplementedError

    def call_soon(self, callback, *args):
        return self.call_later(0, callback, *args)

    def call_later(self, delay, callback, *args):
        raise NotImplementedError

    def call_at(self, when, callback, *args):
        raise NotImplementedError

    def time(self):
        raise NotImplementedError

    def create_future(self):
        raise NotImplementedError

    # Method scheduling a coroutine object: create a task.

    def create_task(self, coro):
        raise NotImplementedError

    # Methods for interacting with threads.

    def call_soon_threadsafe(self, callback, *args):
        raise NotImplementedError

    def run_in_executor(self, executor, func, *args):
        raise NotImplementedError

    def set_default_executor(self, executor):
        raise NotImplementedError

插入新的事件循环

Asyncio旨在支持遵循其API的事件循环的多种实现。 关键是EventLoopPolicy类,该类配置asyncio并允许控制事件循环的各个方面。 这是一个基于libuv的名为uvloop的自定义事件循环的示例,应该比其他方法快得多(我自己尚未对其进行基准测试):

import asyncio
import uvloop
asyncio.set_event_loop_policy(uvloop.EventLoopPolicy())

而已。 现在,无论何时使用任何异步功能,它uvloop

协程,期货和任务

协程是一个有条件的术语。 它既是异步执行的功能,又是需要调度的对象。 您可以通过在定义之前添加async关键字来定义它们:

import asyncio


async def cool_coroutine():
    return "So cool..."

如果调用此类函数,则该函数不会运行。 相反,它返回一个协程对象,并且如果您不计划执行该对象,那么也会收到警告:

c = cool_coroutine()
print(c)

Output:

<coroutine object cool_coroutine at 0x108a862b0>
sys:1: RuntimeWarning: coroutine 'cool_coroutine' was never awaited

Process finished with exit code 0

要实际执行协程,我们需要一个事件循环:

r = loop.run_until_complete(c)
loop.close()

print(r)

Output:

So cool...

那是直接调度。 您还可以链接协程。 请注意,调用协程时必须调用await

import asyncio

async def compute(x, y):
    print("Compute %s + %s ..." % (x, y))
    await asyncio.sleep(1.0)
    return x + y

async def print_sum(x, y):
    result = await compute(x, y)
    print("%s + %s = %s" % (x, y, result))

loop = asyncio.get_event_loop()
loop.run_until_complete(print_sum(1, 2))
loop.close()

该ASYNCIO未来类是类似于concurrent.future.Future类。 它不是线程安全的,并且支持以下功能:

  • 添加和删​​除完成的回调
  • 取消
  • 设定结果和例外

这是在事件循环中使用Future的方法。 take_your_time()协程接受将来,并在睡眠一秒钟后设置结果。

ensure_future()函数安排协程, wait_until_complete()等待将来完成。 在幕后,它为将来增加了完成的回调。

import asyncio

async def take_your_time(future):
    await asyncio.sleep(1)
    future.set_result(42)

loop = asyncio.get_event_loop()
future = asyncio.Future()
asyncio.ensure_future(take_your_time(future))
loop.run_until_complete(future)
print(future.result())
loop.close()

这很麻烦。 Asyncio提供的任务使与期货和协程的工作变得更加愉快。 Task是Future的子类,它包装了协程并且可以取消。

协程不必接受明确的未来并设置其结果或异常。 这是对任务执行相同操作的方法:

import asyncio

async def take_your_time():
    await asyncio.sleep(1)
    return 42

loop = asyncio.get_event_loop()
task = loop.create_task(take_your_time())
loop.run_until_complete(task)
print(task.result())
loop.close()

传输,协议和流

传输是通信通道的抽象。 传输始终支持特定协议。 Asyncio为TCP,UDP,SSL和子进程管道提供了内置的实现。

如果您熟悉基于套接字的网络编程,那么您会感觉很熟悉传输和协议。 使用Asyncio,您可以以标准方式进行异步网络编程。 让我们看一下臭名昭著的echo服务器和客户端(网络的“ hello world”)。

首先,echo客户端实现了一个名为EchoClient的类,该类是从asyncio.Protocol派生的。 它保持其事件循环,并在连接后将一条消息发送到服务器。

connection_made()回调中,它将其消息写入传输。 在data_received()方法中,它仅打印服务器的响应,而在connection_lost()方法中,它停止事件循环。 当将EchoClient类的实例传递到循环的create_connection()方法时,结果是一个协程,循环将一直运行直到完成。

import asyncio

class EchoClient(asyncio.Protocol):
    def __init__(self, message, loop):
        self.message = message
        self.loop = loop

    def connection_made(self, transport):
        transport.write(self.message.encode())
        print('Data sent: {!r}'.format(self.message))

    def data_received(self, data):
        print('Data received: {!r}'.format(data.decode()))

    def connection_lost(self, exc):
        print('The server closed the connection')
        print('Stop the event loop')
        self.loop.stop()

loop = asyncio.get_event_loop()
message = 'Hello World!'
coro = loop.create_connection(lambda: EchoClient(message, loop),
                              '127.0.0.1', 8888)
loop.run_until_complete(coro)
loop.run_forever()
loop.close()

该服务器与之类似,只不过它永远运行,等待客户端连接。 发送回显响应后,它还会关闭与客户端的连接,并准备好与下一个客户端进行连接。

为每个连接创建一个新的EchoServer实例,因此,即使多个客户端同时连接,也不会出现与transport属性冲突的问题。

import asyncio

class EchoServer(asyncio.Protocol):
    def connection_made(self, transport):
        peername = transport.get_extra_info('peername')
        print('Connection from {}'.format(peername))
        self.transport = transport

    def data_received(self, data):
        message = data.decode()
        print('Data received: {!r}'.format(message))

        print('Send: {!r}'.format(message))
        self.transport.write(data)

        print('Close the client socket')
        self.transport.close()

loop = asyncio.get_event_loop()
# Each client connection will create a new protocol instance
coro = loop.create_server(EchoServer, '127.0.0.1', 8888)
server = loop.run_until_complete(coro)
print('Serving on {}'.format(server.sockets[0].getsockname()))
loop.run_forever()

这是连接两个客户端之后的输出:

Serving on ('127.0.0.1', 8888)
Connection from ('127.0.0.1', 53248)
Data received: 'Hello World!'
Send: 'Hello World!'
Close the client socket
Connection from ('127.0.0.1', 53351)
Data received: 'Hello World!'
Send: 'Hello World!'
Close the client socket

流提供了基于协程的高级API,并提供了Reader和Writer抽象。 协议和传输是隐藏的,不需要定义自己的类,也没有回调。 您只需等待连接和接收到的数据等事​​件。

客户端调用open_connection()函数,该函数返回自然使用的读取器和写入器对象。 要关闭连接,它将关闭编写器。

import asyncio


async def tcp_echo_client(message, loop):
    reader, writer = await asyncio.open_connection(
        '127.0.0.1', 
        8888, 
        loop=loop)

    print('Send: %r' % message)
    writer.write(message.encode())

    data = await reader.read(100)
    print('Received: %r' % data.decode())

    print('Close the socket')
    writer.close()


message = 'Hello World!'
loop = asyncio.get_event_loop()
loop.run_until_complete(tcp_echo_client(message, loop))
loop.close()

服务器也大大简化了。

import asyncio

async def handle_echo(reader, writer):
    data = await reader.read(100)
    message = data.decode()
    addr = writer.get_extra_info('peername')
    print("Received %r from %r" % (message, addr))

    print("Send: %r" % message)
    writer.write(data)
    await writer.drain()

    print("Close the client socket")
    writer.close()

loop = asyncio.get_event_loop()
coro = asyncio.start_server(handle_echo, 
                            '127.0.0.1', 
                            8888, 
                            loop=loop)
server = loop.run_until_complete(coro)
print('Serving on {}'.format(server.sockets[0].getsockname()))
loop.run_forever()

处理子流程

Asyncio也涵盖了与子流程的交互。 以下程序启动另一个Python进程,并执行代码“ import this”。 它是Python著名的复活节彩蛋之一,上面印有“ Zen of Python”。 查看下面的输出。

使用create_subprocess_exec()函数在zen()create_subprocess_exec() Python进程,并将标准输出绑定到管道。 然后,使用await逐行迭代标准输出,以使其他进程或协程有机会在输出尚未准备好时执行。

请注意,在Windows上,您必须将事件循环设置为ProactorEventLoop因为标准的SelectorEventLoop不支持管道。

import asyncio.subprocess
import sys


async def zen():
    code = 'import this'
    create = asyncio.create_subprocess_exec(
        sys.executable, 
        '-c', 
        code,
        stdout=asyncio.subprocess.PIPE)
    proc = await create

    data = await proc.stdout.readline()
    while data:
        line = data.decode('ascii').rstrip()
        print(line)
        data = await proc.stdout.readline()

    await proc.wait()

if sys.platform == "win32":
    loop = asyncio.ProactorEventLoop()
    asyncio.set_event_loop(loop)
else:
    loop = asyncio.get_event_loop()

loop.run_until_complete(zen())

Output:

The Zen of Python, by Tim Peters

Beautiful is better than ugly.
Explicit is better than implicit.
Simple is better than complex.
Complex is better than complicated.
Flat is better than nested.
Sparse is better than dense.
Readability counts.
Special cases aren't special enough to break the rules.
Although practicality beats purity.
Errors should never pass silently.
Unless explicitly silenced.
In the face of ambiguity, refuse the temptation to guess.
There should be one-- and preferably only one --obvious way to
do it.
Although that way may not be obvious at first unless you're
Dutch.
Now is better than never.
Although never is often better than *right* now.
If the implementation is hard to explain, it's a bad idea.
If the implementation is easy to explain, it may be a good idea.
Namespaces are one honking great idea -- let's do more of those!

结论

不要犹豫,看看我们在市场上可以出售和学习的东西 ,也不要犹豫,使用下面的提要来问任何问题并提供宝贵的反馈。

Python的asyncio是异步编程的综合框架。 它具有广阔的范围,并支持低级和高级API。 它仍然相对年轻,并没有为社区所了解。

我相信,随着时间的流逝,最佳实践将不断涌现,更多示例将浮出水面,并使使用此功能强大的库变得更加容易。

翻译自: https://code.tutsplus.com/tutorials/asynchronous-io-with-python-3--cms-29045

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值