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 异步编程技术。
超级会员免费看
942

被折叠的 条评论
为什么被折叠?



