Python开发(Asyncio简介)

本文概述

  • 定义
  • 异步等待
  • 一个不好的协程示例
  • 一个更好的协程示例
  • 安排电话
  • 任务
  • 包起来
asyncio模块在3.4版中作为临时包添加到了Python中。这意味着asyncio可能会收到向后不兼容的更改, 甚至可能在将来的Python版本中删除。
根据该文档, asyncio” 提供了使用协程编写单线程并发代码, 通过套接字和其他资源进行I / O复用访问, 运行网络客户端和服务器以及其他相关原语的基础结构” 。本章并不打算涵盖你可以使用asyncio进行的所有操作, 但是你将学习如何使用该模块以及它为何有用。
如果你在旧版本的Python中需要类似asyncio的东西, 那么你可能想看看Twisted或gevent。
定义asyncio模块提供了一个围绕事件循环的框架。事件循环基本上等待事件发生, 然后对事件进行操作。它负责处理I / O和系统事件。
asyncio实际上有几个可用的循环实现。默认情况下, 该模块将是运行该模块的操作系统中效率最高的模块。但是, 你可以根据需要明确选择事件循环。事件循环基本上说” 事件A发生时, 对功能B做出反应” 。
当服务器在等待某人来请求资源(例如网页)时, 请考虑一下服务器。如果该网站不是很受欢迎, 则服务器将长时间闲置。但是, 当确实受到打击时, 服务器需要做出反应。这种反应称为事件处理。当用户加载网页时, 服务器将检查并调用一个或多个事件处理程序。这些事件处理程序完成后, 它们需要将控制权交还给事件循环。为此, asyncio使用协程。
协程是一种特殊功能, 可以在不丢失其状态的情况下放弃对其调用方的控制。协程是消费者, 是生成器的扩展。与线程相比, 它们的一大优点是它们不需要占用太多内存来执行。请注意, 调用协程函数时, 该函数实际上不会执行。相反, 它将返回一个协程对象, 你可以将其传递到事件循环以使其立即执行或稍后执行。
使用asyncio模块时, 你可能会碰到另一个术语。基本上, 未来是代表尚未完成的工作结果的对象。你的事件循环可以监视将来的对象并等待它们完成。将来完成时, 它将设置为完成。 Asyncio还支持锁和信号灯。
我要提及的最后一条信息是” 任务” 。任务是协程的包装, 也是Future的子类。你甚至可以使用事件循环安排任务。
异步等待在Python 3.5中添加了async和await关键字, 以定义本机协程, 并使其与基于生成器的协程相比具有不同的类型。如果你想深入了解异步和等待状态, 请查看PEP 492。
在Python 3.4中, 你将创建一个这样的协程:
该装饰器仍可在Python 3.5中使用, 但类型模块收到了协程函数形式的更新, 该更新现在将告诉你与之交互的是本机协程。从Python 3.5开始, 你可以使用async def在语法上定义协程函数。
因此, 上面的函数最终看起来像这样:
以这种方式定义协程时, 不能在协程函数内部使用yield。相反, 它必须包含用于将值返回给调用方的return或await语句。请注意, 关键字await只能在异步def函数中使用。
可以将async / await关键字视为用于异步编程的API。 asyncio模块只是碰巧使用async / await进行异步编程的框架。实际上, 有一个名为curio的项目可以证明这一概念, 因为它是事件循环的单独实现, 该事件循环在幕后使用了async / wait。
一个不好的协程示例尽管有很多背景信息对所有这些工作原理无疑是有帮助的, 但有时你只想看一些示例, 以便对语法以及如何将它们组合在一起有个了解。
因此, 请记住一个简单的例子!
你将要完成的一个相当普通的任务是从某个位置下载文件, 无论是内部资源还是Internet上的文件。通常, 你将要下载多个文件。
因此, 让我们创建一对可以做到这一点的协程:
import asyncioimport osimport urllib.request async def download_coroutine(url):#"A coroutine to download the specified url"request = urllib.request.urlopen(url)filename = os.path.basename(url) with open(filename, 'wb') as file_handle:while True:chunk = request.read(1024)if not chunk:breakfile_handle.write(chunk)msg = 'Finished downloading {filename}'.format(filename=filename)return msg async def main(urls):"""Creates a group of coroutines and waits for them to finish"""coroutines = [download_coroutine(url) for url in urls]completed, pending = await asyncio.wait(coroutines)for item in completed:print(item.result()) if __name__ == '__main__':urls = ["http://www.irs.gov/pub/irs-pdf/f1040.pdf", "http://www.irs.gov/pub/irs-pdf/f1040a.pdf", "http://www.irs.gov/pub/irs-pdf/f1040ez.pdf", "http://www.irs.gov/pub/irs-pdf/f1040es.pdf", "http://www.irs.gov/pub/irs-pdf/f1040sb.pdf"] event_loop = asyncio.get_event_loop()try:event_loop.run_until_complete(main(urls))finally:event_loop.close()

在这段代码中, 我们导入所需的模块, 然后使用异步语法创建第一个协程。这个协程称为download_coroutine, 它使用Python的urllib下载传递给它的所有URL。完成后, 它将返回一条消息, 内容是这样的。
另一个协程是我们的主要协程。它基本上获取一个或多个URL的列表并将它们排队。我们使用asyncio的wait函数来等待协程完成。当然, 要实际启动协程, 需要将它们添加到事件循环中。我们在获得事件循环的最后执行此操作, 然后调用其run_until_complete方法。你会注意到, 我们将主协程传递给了事件循环。这开始运行主协程, 该协程将第二个协程排入队列并开始运行。这被称为链式协程。
这个例子的问题在于它根本不是协程。原因是download_coroutine函数不是异步的。这里的问题是urllib不是异步的, 而且, 我也没有使用await或yield from这两者。更好的方法是使用aiohttp包。让我们接下来看看!
一个更好的协程示例aiohttp软件包用于创建异步HTTP客户端和服务器。你可以使用pip安装它, 如下所示:
pip install aiohttp

安装完成后, 让我们更新代码以使用aiohttp, 以便我们下载文件:
你会在这里注意到我们导入了两个新项目:aiohttp和async_timeout。后者实际上是aiohttp的依赖项之一, 它使我们可以创建超时上下文管理器。让我们从代码的底部开始, 然后逐步进行。在最下面的条件语句中, 我们开始异步事件循环并调用我们的main函数。
在主函数中, 我们创建一个ClientSession对象, 然后将其传递给要下载的每个URL的下载协程函数。在download_coroutine中, 我们创建一个async_timeout.timeout()上下文管理器, 该管理器基本上创建一个X秒的计时器。当秒数用完时, 上下文管理器结束或超时。在这种情况下, 超时为10秒。接下来, 我们调用会话的get()方法, 该方法为我们提供了一个响应对象。现在, 我们进入了一个神奇的部分。当你使用响应对象的content属性时, 它会返回aiohttp.StreamReader的实例, 该实例使我们能够以任意大小下载文件。读取文件时, 会将其写到本地磁盘。最后, 我们调用响应的release()方法, 该方法将完成响应处理。
根据aiohttp的文档, 由于响应对象是在上下文管理器中创建的, 因此从技术上讲, 它隐式调用release()。但是在Python中, 显式通常会更好, 并且在文档中有一条注释, 我们不应该仅仅依赖于连接, 所以我认为在这种情况下最好发布它。
有一部分仍在这里阻塞, 这是实际写入磁盘的代码部分。在写入文件时, 我们仍在阻止。还有另一个名为aiofiles的库, 我们也可以使用该库来尝试使文件写入异步, 但是我将把更新内容留给读者。
安排电话你还可以使用asyncio事件循环安排对常规函数的调用。我们要看的第一种方法是call_soon。 call_soon方法基本上会尽快调用你的回调或事件处理程序。它用作FIFO队列, 因此, 如果某些回调需要一段时间才能运行, 那么其他回调将被延迟, 直到之前的回调完成为止。
让我们看一个例子:
asyncio的大多数功能都不接受关键字, 因此, 如果需要将关键字传递给事件处理程序, 则需要functools模块。每当调用标准函数时, 它将在stdout中输出一些文本。如果碰巧将其stop参数设置为True, 它也会停止事件循环。
第一次调用它时, 我们不会停止循环。第二次调用它时, 我们确实停止了循环。我们要停止循环的原因是, 我们已将其告知run_forever, 这会将事件循环置于无限循环中。一旦循环停止, 我们就可以关闭它。
如果运行此代码, 则应看到以下输出:
starting event loopEvent handler calledEvent handler calledstopping the loopclosing event loop

有一个名为call_soon_threadsafe的相关函数。顾名思义, 它的工作方式与call_soon相同, 但是它是线程安全的。如果你想将呼叫实际延迟到以后的某个时间, 可以使用call_later函数来进行。在这种情况下, 我们可以将我们的call_soon签名更改为以下内容:
loop.call_later(1, event_handler, loop)

这将延迟调用事件处理程序一秒钟, 然后将其调用并将循环作为第一个参数传入。如果你想安排将来的某个特定时间, 则需要获取循环时间而不是计算机时间。你可以这样做:
current_time = loop.time()

一旦有了它, 就可以使用call_at函数并将其传递给它调用事件处理程序的时间。假设我们要从现在开始五分钟打电话给事件处理程序。你可以按照以下方式操作:
loop.call_at(current_time + 300, event_handler, loop)

在此示例中, 我们使用当前获取的时间, 并将其添加300秒或5分钟。这样, 我们将调用事件处理程序的时间延迟了五分钟!漂亮整齐!
任务任务是Future的子类, 是协程的包装。他们使你能够跟踪完成处理的时间。因为它们是未来的一种, 所以其他协程可以等待任务, 并且在完成任务后也可以获取任务的结果。
让我们看一个简单的例子:
在这里, 我们创建一个异步函数, 该函数接受该函数运行所需的秒数。这模拟了一个长时间运行的过程。然后, 我们创建事件循环, 然后通过调用事件循环对象的create_task函数来创建任务对象。 create_task函数接受我们要转换为任务的函数。然后, 我们告诉事件循环运行, 直到任务完成。最后, 由于任务完成, 我们得到了任务的结果。
通过使用取消方法, 任务也可以非常轻松地取消。要结束任务时只需调用它即可。如果任务在等待其他操作时被取消, 则该任务将引发CancelledError。
包起来在这一点上, 你应该了解足够的知识, 可以自己开始使用asyncio库。 asyncio库非常强大, 可让你执行许多非常酷和有趣的任务。 Theasyncio库是为网络套接字而设计的。
【Python开发(Asyncio简介)】一个很棒的库, 允许在Twisted框架异步之前进行异步套接字编程。另一个有趣的项目是Dask, 它是一个用于分析计算的灵活并行计算库。只需深入研究asyncio模块的文档, 让你的想法开始流行!

    推荐阅读