28、Python异步编程:从asyncio到Tornado的全面指南

Python异步编程:从asyncio到Tornado的全面指南

1. asyncio任务操作

1.1 asyncio.Task简介

asyncio 模块提供了 asyncio.Task() 方法,用于通过任务处理协程。 asyncio.Task 类是 asyncio.Future 的子类,旨在管理协程。任务负责在事件循环中执行协程对象。当协程被包装在任务中时,它会将任务连接到事件循环,并在循环启动时自动运行,从而提供了一种自动驱动协程的机制。

更多关于 asyncio 任务操作的信息,请参考文档: https://docs.python.org/3.7/library/asyncio-task.html

1.2 代码示例

以下是一个使用 asyncio 进行任务操作的示例代码:

#!/usr/bin/python3
import asyncio
import time

@asyncio.coroutine
def task_sleep(name, loop, seconds=1):
    future = loop.run_in_executor(None, time.sleep, seconds)
    print("[%s] coroutine will sleep for %d second(s)..." % (name, seconds))
    yield from future
    print("[%s] done!" % name)

if __name__ == '__main__':
    loop = asyncio.get_event_loop()
    tasks = [asyncio.Task(task_sleep('Task-A', loop, 10)), asyncio.Task(task_sleep('Task-B', loop, 5))]
    loop.run_until_complete(asyncio.gather(*tasks))

1.3 代码解释

  • task_sleep 函数是一个协程,它使用 loop.run_in_executor 方法在另一个线程中执行 time.sleep 函数。
  • asyncio.gather 方法用于并行运行多个任务。
  • loop.run_until_complete 方法用于运行事件循环,直到所有任务完成。

1.4 输出结果

[Task-A] coroutine will sleep for 10 second(s)...
[Task-B] coroutine will sleep for 5 second(s)...
[Task-B] done!
[Task-A] done!

从输出结果可以看出,任务的完成顺序取决于定义的睡眠时间。

2. 使用asyncio下载文件

2.1 代码示例

以下是一个使用 asyncio 异步下载文件的示例代码:

#!/usr/bin/python3
import asyncio
import os
import requests
import time

files = ['https://docs.python.org/3/archives/python-3.7.2-docs-pdf-letter.zip', 'https://docs.python.org/3/archives/python-3.7.2-docs-pdf-a4.zip']

async def download_file(url):
    response = requests.get(url)
    filename = os.path.basename(url)
    print('Downloading {filename}'.format(filename=filename))
    open(filename, 'wb').write(response.content)
    msg = 'Finished downloading {filename}'.format(filename=filename)
    return msg

async def main(files):
    coroutines = [download_file(file) for file in files]
    completed, pending = await asyncio.wait(coroutines)
    for item in completed:
        print(item.result())

if __name__ == '__main__':
    t1 = time.time()
    event_loop = asyncio.get_event_loop()
    try:
        event_loop.run_until_complete(main(files))
    finally:
        event_loop.close()
    print(time.time() - t1, 'seconds passed')

2.2 代码解释

  • download_file 函数是一个协程,它使用 requests 模块下载文件,并将文件保存到本地。
  • main 函数使用 asyncio.wait 方法并行运行多个下载任务。
  • event_loop.run_until_complete 方法用于运行事件循环,直到所有下载任务完成。

2.3 输出结果

Downloading python-3.7.2-docs-pdf-letter.zip
Downloading python-3.7.2-docs-pdf-a4.zip
Finished downloading python-3.7.2-docs-pdf-letter.zip
Finished downloading python-3.7.2-docs-pdf-a4.zip
11.149724960327148 seconds passed

从输出结果可以看出,文件的下载顺序是并行的,并且可以看到下载文件的执行时间。

3. 引入aiohttp

3.1 aiohttp简介

aiohttp 是一个与 asyncio 经常一起使用的模块,它提供了一个处理异步请求的框架。它是用 Python 3.5+ 编写的 Web 应用程序服务器部分的优秀解决方案。

requests 模块是进行请求的主要工具,但它的主要问题是线程会被阻塞,直到获得响应。默认情况下,请求操作是阻塞的。而 aiohttp 允许我们异步地进行请求。

你可以使用以下命令安装 aiohttp

pip install aiohttp

aiohttp 的文档可以在 http://aiohttp.readthedocs.io/en/stable 找到,源代码可以在 https://github.com/aio-libs/aiohttp 找到。

3.2 aiohttp请求示例

以下是一个使用 aiohttp 进行请求的示例代码:

#!/usr/local/bin/python3
import asyncio
from aiohttp import ClientSession
import time

async def request():
    async with ClientSession() as session:
        async with session.get("http://httpbin.org/headers") as response:
            response = await response.read()
            print(response.decode())

if __name__ == '__main__':
    t1 = time.time()
    loop = asyncio.get_event_loop()
    loop.run_until_complete(request())
    print(time.time() - t1, 'seconds passed')

3.3 代码解释

  • ClientSession aiohttp 推荐的主要请求接口,它允许在请求之间存储 cookie,并保持所有请求的公共对象(事件循环、连接和访问资源)。
  • async with 语句用于确保会话在所有情况下都能正确关闭。
  • loop.run_until_complete 方法用于运行事件循环,直到请求完成。

3.4 输出结果

{
    "headers": {
        "Accept": "*/*",
        "Accept-Encoding": "gzip, deflate",
        "Host": "httpbin.org",
        "User-Agent": "Python/3.6 aiohttp/3.5.4"
    }
}

3.5 另一个aiohttp请求示例

#!/usr/bin/python3
import asyncio
import aiohttp

url = 'http://httpbin.org/headers'

@asyncio.coroutine
def get_page():
    resp = yield from aiohttp.ClientSession().get(url)
    text = yield from resp.read()
    return text

if __name__ == '__main__':
    loop = asyncio.get_event_loop()
    content = loop.run_until_complete(get_page())
    print(content)
    loop.close()

3.6 输出结果

Unclosed client session
client_session: <aiohttp.client.ClientSession object at 0x000001BFE94117F0>
Unclosed connector
connections: ['[(<aiohttp.client_proto.ResponseHandler object at 0x000001BFE954F708>, 789153.843)]']
connector: <aiohttp.connector.TCPConnector object at 0x000001BFE9411EB8>
b'{\n  "headers": {\n    "Accept": "*/*", \n    "Accept-Encoding": "gzip, deflate", \n    "Host": "httpbin.org", \n    "User-Agent": "Python/3.6 aiohttp/3.5.4"\n  }\n}'

4. 使用aiohttp下载文件

4.1 代码示例

以下是一个使用 aiohttp 异步下载文件的示例代码:

#!/usr/bin/python3
import asyncio
import itertools
import aiohttp
import time
import os

async def download_file(url, parts):
    async def get_partial_content(u, i, start, end):
        async with aiohttp.ClientSession().get(u, headers={"Range": "bytes={}-{}".format(start, end - 1)}) as _resp:
            return i, await _resp.read()
    async with aiohttp.ClientSession().head(url) as resp:
        size = int(resp.headers["Content-Length"])
    ranges = list(range(0, size, size // parts))
    response, _ = await asyncio.wait([get_partial_content(url, i, start, end) for i, (start, end) in enumerate(zip(ranges, ranges[1:] + [size]))])
    final_result = sorted(task.result() for task in response)
    return b"".join(data for _, data in final_result)

if __name__ == '__main__':
    file_url = 'https://docs.python.org/3/archives/python-3.7.2-docs-pdf-letter.zip'
    loop = asyncio.get_event_loop()
    t1 = time.time()
    bs = loop.run_until_complete(download_file(file_url, 10))
    filename = os.path.basename(file_url)
    with open(filename, 'wb') as file_handle:
        file_handle.write(bs)
    print('Finished downloading {filename}'.format(filename=filename))
    print(time.time() - t1, 'seconds passed')

4.2 代码解释

  • download_file 函数接受文件的 URL 和并行请求的数量作为参数。
  • get_partial_content 函数用于下载文件的部分内容。
  • asyncio.wait 方法用于并行运行多个下载任务。
  • sorted 方法用于对下载的部分内容进行排序。
  • b"".join 方法用于将下载的部分内容合并成一个完整的文件。

4.3 输出结果

client_session: <aiohttp.client.ClientSession object at 0x000001FABBB42DD8>
Unclosed connector
connections: ['[(<aiohttp.client_proto.ResponseHandler object at 0x000001FABBCEED08>, 715247.328)]']
connector: <aiohttp.connector.TCPConnector object at 0x000001FABBCE6C88>
Finished downloading python-3.7.2-docs-pdf-letter.zip
2.9168717861175537 seconds passed

从输出结果可以看出,文件的下载时间明显缩短。

5. 其他事件循环解决方案

事件循环可以定义为使用轮询函数来监控事件的抽象。内部,事件循环使用轮询器对象,减轻了程序员控制事件添加、删除和控制任务的责任。以下是一些在 Python 中实现事件循环的应用示例:
| 解决方案 | 描述 |
| ---- | ---- |
| Tornado web server (http://www.tornadoweb.org/en/stable) | 如果环境是 Linux,Tornado 使用 epoll 作为轮询函数;在 BSD 或 Mac OS X 中支持 kqueue 。 |
| Twisted (https://twistedmatrix.com) | 一个流行的框架,提供了事件循环的实现,被 Scrapy 框架使用。 |
| Gevent (http://www.gevent.org) | 提供基于 libev 的事件循环。 |
| Eventlet (https://pypi.python.org/pypi/eventlet) | 实现基于 libevent 的事件循环。 |

5.1 mermaid流程图

graph LR
    A[事件循环] --> B[Tornado]
    A --> C[Twisted]
    A --> D[Gevent]
    A --> E[Eventlet]

以上就是关于 Python 异步编程中 asyncio aiohttp 的介绍,以及一些其他事件循环解决方案的说明。在接下来的部分,我们将介绍如何使用 Tornado 框架构建异步网络应用程序。

6. 构建异步网络应用程序:Tornado 框架

6.1 Tornado 简介

传统创建支持多个客户端并发的应用程序(如 Web 服务器)的模型基于多线程系统,即为每个连接到服务的客户端创建一个新线程。这会导致系统资源消耗较高和性能问题,有时问题还会很严重。

Tornado 是用 Python 编写的模块,可用于创建异步和非阻塞的网络操作系统,其中客户端执行的每个请求都可以是异步的。该库的实现方式使其能够扩展到数千个开放连接,非常适合需要长生命周期连接的应用程序。

你可以使用以下命令安装 Tornado:

pip install tornado

也可以从 GitHub 仓库(https://github.com/tornadoweb/tornado)下载最新版本,并使用 setup.py 脚本手动安装。Tornado 可被视为 Twisted 的替代方案,适合处理大量连接,因为它可以响应传入的客户端请求,将请求发送到控制器,直到获得调用结果后才将控制权返回给客户端。异步处理有助于功能解耦和访问共享数据,这在无状态设计(如 REST)或其他面向服务的架构中效果很好。更多关于该框架和源代码的信息可在 GitHub 仓库中找到。

6.2 实现 Tornado Web 服务器

Tornado 有多个类和函数,可用于创建不同类型的网络元素,包括同步和异步的。这里我们将重点介绍使用 Tornado 创建服务器和 Web 应用程序的模块,这对于进行概念验证和理解 Web 环境中某些功能的操作很有用。

以下是使用 Tornado 创建基本 Web 服务器的脚本:

#!/usr/bin/python3
import tornado.ioloop
import tornado.web
from tornado.options import define, options

class MyHandler(tornado.web.RequestHandler):
    def get(self):
        self.render("index.html")

if __name__ == '__main__':
    define("port", default=8080, help="run on the given port", type=int)
    app = tornado.web.Application([('/', MyHandler)])
    app.listen(options.port)
    print("Tornado web server listening on port 8080")
    tornado.ioloop.IOLoop.instance().start()
代码解释:
  • tornado.web.Application 对象负责定义 Web 服务器可用的 URI。在这个例子中,定义了用户可以访问路径 '/' 。如果用户请求资源 '/' ,服务器将执行 MyHandler 处理程序。
  • MyHandler 类继承自 tornado.web.RequestHandler 类,负责处理客户端使用 GET 方法发出的 HTTP 请求。在这种情况下,该类只是用 index.html 页面响应客户端。
  • 最后,Web 服务器的实际定义由 tornado.ioloop.IOLoop 类的实例给出,它负责创建一个无限运行的线程,并使用通过 tornado.options.define 函数定义的命令行选项。

运行该 Web 服务器的命令如下:

python tornado_web_server.py

执行上述命令后,控制台将显示以下消息:

Tornado web server listening on port 8080

如果用户请求资源 '/' ,服务器将用 index.html 页面响应。

6.3 实现异步客户端:AsyncHTTPClient

Tornado 包含一个名为 AsyncHTTPClient 的类,用于异步执行 HTTP 请求。首先要创建应用程序,它将继承自 Application 。然后运行支持该应用程序的 HTTP 服务器,接着指定服务器监听的端口。最后启动事件循环,使用 IOLoop.current().start() 指令监听请求。

以下是一个使用 AsyncHTTPClient fetch 方法的示例,该方法将在 HTTP 请求完成时调用的方法或函数作为回调参数指定。在这个例子中,指定了 on_response 方法作为回调。同时注意 @tornado.web.asynchronous 装饰器的使用以及在回调响应方法末尾调用 self.finish()

#!/usr/bin/python3
import tornado.ioloop
import tornado.web
import tornado.httpclient

class Handler(tornado.web.RequestHandler):
    @tornado.web.asynchronous
    def get(self):
        http_client = tornado.httpclient.AsyncHTTPClient()
        http_client.fetch("https://www.google.com/search?q=python", callback=self.on_response)

    def on_response(self, response):
        self.write(response.body)
        self.finish()

if __name__ == '__main__':
    app = tornado.web.Application([tornado.web.url(r"/", Handler)])
    app.listen(8080)
    tornado.ioloop.IOLoop.current().start()
代码解释:
  • 定义了 Handler 类,它继承自 tornado.web.RequestHandler 。该类包含异步的 get() 方法和 on_response() 方法,当从 http_client 对象获得响应时调用 on_response() 方法。
  • 在主程序中,定义了事件循环和由 Handler 类管理的应用程序。

如果执行此脚本并访问 http://localhost:8080 ,将看到与在 Google 域名中搜索 Python 相关的响应。

6.4 另一种实现异步客户端的方式

还可以创建一个 TornadoAsyncClient() 类,该类有一个处理请求的方法。在这个例子中,URL 作为脚本的参数被请求。

#!/usr/bin/python3
import argparse
import tornado.ioloop
import tornado.httpclient

class TornadoAsyncClient():
    def handle_request(self, response):
        if response.error:
            print("Error:", response.error)
        else:
            print(response.body)
        tornado.ioloop.IOLoop.instance().stop()

def run_server(url):
    tornadoAsync = TornadoAsyncClient()
    http_client = tornado.httpclient.AsyncHTTPClient()
    http_client.fetch(url, tornadoAsync.handle_request)
    tornado.ioloop.IOLoop.instance().start()

if __name__ == '__main__':
    parser = argparse.ArgumentParser(description='Tornado async client')
    parser.add_argument('--url', action="store", dest="url", type=str, required=True)
    given_args = parser.parse_args()
    url = given_args.url
    run_server(url)
代码解释:
  • 定义了 TornadoAsyncClient 类,用于管理请求和事件循环。
  • run_server 方法实例化 TornadoAsyncClient 类,启动事件循环,并设置要请求的 URL 参数。

执行此脚本时,需要将想要获取响应的 URL 作为参数传递:

python tornado_async_client.py --url <your_url>

运行上述命令后,将看到作为参数传递的 URL 的响应体。

6.5 mermaid 流程图

graph LR
    A[启动 Tornado 应用] --> B[创建 AsyncHTTPClient]
    B --> C[发起 HTTP 请求]
    C --> D{请求完成?}
    D -- 是 --> E[处理响应]
    D -- 否 --> C
    E --> F[停止事件循环]

6.6 总结

本文全面介绍了 Python 异步编程的相关内容。从 asyncio 的任务操作开始,我们了解了如何使用 asyncio.Task 管理协程,以及如何通过协程实现文件下载。接着引入了 aiohttp 模块,它为异步请求提供了强大的支持,能有效避免 requests 模块的阻塞问题。之后介绍了多种事件循环解决方案,如 Tornado、Twisted、Gevent 和 Eventlet。最后详细讲解了 Tornado 框架,包括如何使用它创建 Web 服务器和异步客户端。通过这些技术,我们可以构建高效、可扩展的异步网络应用程序,提升系统性能和响应能力。

以下是本文涉及的主要技术和操作步骤总结:
| 技术 | 操作步骤 |
| ---- | ---- |
| asyncio 任务操作 | 1. 定义协程函数;2. 使用 asyncio.Task 包装协程;3. 使用 asyncio.gather 并行运行任务;4. 使用 loop.run_until_complete 运行事件循环。 |
| asyncio 下载文件 | 1. 定义下载文件的协程函数;2. 创建协程列表;3. 使用 asyncio.wait 并行运行协程;4. 运行事件循环直到任务完成。 |
| aiohttp 请求 | 1. 安装 aiohttp ;2. 使用 ClientSession 创建会话;3. 发起异步请求;4. 处理响应。 |
| aiohttp 下载文件 | 1. 定义下载文件的协程函数,将文件分块下载;2. 并行运行下载任务;3. 合并下载的文件块。 |
| Tornado Web 服务器 | 1. 安装 Tornado;2. 定义请求处理类;3. 创建 Application 对象;4. 启动事件循环。 |
| Tornado 异步客户端 | 1. 创建 AsyncHTTPClient ;2. 发起异步请求并指定回调函数;3. 处理响应。 |

希望这些内容能帮助你更好地理解和应用 Python 异步编程技术。

【完美复现】面向配电网韧性提升的移动储能预布局与动态调度策略【IEEE33节点】(Matlab代码实现)内容概要:本文介绍了基于IEEE33节点的配电网韧性提升方法,重点研究了移动储能系统的预布局与动态调度策略。通过Matlab代码实现,提出了一种结合预配置和动态调度的两阶段优化模型,旨在应对电网故障或极端事件时快速恢复供电能力。文中采用了多种智能优化算法(如PSO、MPSO、TACPSO、SOA、GA等)进行对比分析,验证所提策略的有效性和优越性。研究不仅关注移动储能单元的初始部署位置,还深入探讨其在故障发生后的动态路径规划与电力支援过程,从而全面提升配电网的韧性水平。; 适合人群:具备电力系统基础知识和Matlab编程能力的研究生、科研人员及从事智能电网、能源系统优化等相关领域的工程技术人员。; 使用场景及目标:①用于科研复现,特别是IEEE顶刊或SCI一区论文中关于配电网韧性、应急电源调度的研究;②支撑电力系统在灾害或故障条件下的恢复力优化设计,提升实际电网应对突发事件的能力;③为移动储能系统在智能配电网中的应用提供理论依据和技术支持。; 阅读建议:建议读者结合提供的Matlab代码逐模块分析,重点关注目标函数建模、约束条件设置以及智能算法的实现细节。同时推荐参考文中提及的MPS预配置与动态调度上下两部分,系统掌握完整的技术路线,并可通过替换不同算法或测试系统进一步拓展研究。
先看效果: https://pan.quark.cn/s/3756295eddc9 在C#软件开发过程中,DateTimePicker组件被视为一种常见且关键的构成部分,它为用户提供了图形化的途径来选取日期与时间。 此类控件多应用于需要用户输入日期或时间数据的场景,例如日程管理、订单管理或时间记录等情境。 针对这一主题,我们将细致研究DateTimePicker的操作方法、具备的功能以及相关的C#编程理念。 DateTimePicker控件是由.NET Framework所支持的一种界面组件,适用于在Windows Forms应用程序中部署。 在构建阶段,程序员能够通过调整属性来设定其视觉形态及运作模式,诸如设定日期的显示格式、是否展现时间选项、预设的初始值等。 在执行阶段,用户能够通过点击日历图标的下拉列表来选定日期,或是在文本区域直接键入日期信息,随后按下Tab键或回车键以确认所选定的内容。 在C#语言中,DateTime结构是处理日期与时间数据的核心,而DateTimePicker控件的值则表现为DateTime类型的实例。 用户能够借助`Value`属性来读取或设定用户所选择的日期与时间。 例如,以下代码片段展示了如何为DateTimePicker设定初始的日期值:```csharpDateTimePicker dateTimePicker = new DateTimePicker();dateTimePicker.Value = DateTime.Now;```再者,DateTimePicker控件还内置了事件响应机制,比如`ValueChanged`事件,当用户修改日期或时间时会自动激活。 开发者可以注册该事件以执行特定的功能,例如进行输入验证或更新关联的数据:``...
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值