python asyncio异步编程

文章目录

  • 一、asyncio(async await)异步编程
    • 1.初识asyncio
    • 2.什么是协程?
      • 2.1 greenlet实现协程
      • 2.2 yield关键字实现协程
      • 2.3 asyncio异步实现协程
      • 2.4 async & await关键字
    • 3.异步编程
      • 3.1 协程的意义
      • 3.2 事件循环
      • 3.3 快速上手
      • 3.4 异步迭代器
      • 3.5 异步上下文管理
    • 4.异步实战案例
      • 4.1 uvloop
      • 4.2 异步redis
      • 4.3 异步MySQL
      • 4.4 FastAPI框架


一、asyncio(async await)异步编程

1.初识asyncio

时代的变革非常之快,对于异步的需求越来越大,有非常多的框架已经开始朝着异步的方向走去,比如tornado、fastapi、channels、aiohttp以及我们的django 3.x版本中的asgi模块也进入了异步的时代,因为对于异步而言能使我们的性能将大大提高。

2.什么是协程?

对于协程而言,并不是计算机所提供的,计算机所提供的只有进程和线程、而协程则是通过程序员人为所创造的。我们通常将协程称为微线程,是一种用户态的轻量级线程。协程不像线程和进程那样,需要进行系统内核上的上下文切换,协程的上下文切换是由程序员决定的。

那么实现协程的方法有以下几种:

  • greenlet(早期模块)
  • yield关键字
  • asyncio装饰器(需py3.4以上)
  • async、await关键字(需py3.5以上,并且推荐使用该方法)

那么具体实现方法我们往下面看

2.1 greenlet实现协程

使用前先安装pip install greenlet

代码如下:

from greenlet import greenlet


def func1():
   print(1)
   # 切换到func2
   gr2.switch()
   print(2)
   gr2.switch()


def func2():
   print(3)
   gr1.switch()
   print(4)
   gr1.switch()


if __name__ == '__main__':
   gr1 = greenlet(func1)
   gr2 = greenlet(func2)
   # 第一步执行func1
   gr1.switch()

此时控制台则打印了1,3,2,4如图:
python asyncio异步编程_第1张图片
那么通过greenlet模块就实现了通过一个线程实现代码块相互切换的操作。

2.2 yield关键字实现协程

当然通过yield关键字也可以实现,代码如下:

def func1():
   yield 1
   yield from func2()
   yield 2


def func2():
   yield 3
   yield 4


if __name__ == '__main__':
   f1 = func1()
   for item in f1:
       print(item)

此时控制台打印了1,3,2,4,和上图一样,但是对于yield 关键字实现的协程并不比greenlet模块灵活,所以这里大家就了解一下yield关键字也可以实现协程就可以了。

2.3 asyncio异步实现协程

如果想使用asyncio异步,我们需要在python3.4及之后的版本,对于asyncio而言它比上面的那两个强大很多,区别在于前面两个方法是我们人工手动切换的,而asyncio在遇到IO阻塞的时候是会自动切换到其他任务中。

asyncio示例代码如下:

import asyncio


@asyncio.coroutine
def func1():
   print(1)
   yield from asyncio.sleep(2)  # 遇到IO耗时操作,自动切换到tasks中的其他任务
   print(2)


@asyncio.coroutine
def func2():
   print(3)
   yield from asyncio.sleep(2)  # 遇到IO耗时操作,自动切换到tasks中的其他任务
   print(4)


tasks = [
   asyncio.ensure_future(func1()),
   asyncio.ensure_future(func2())
]

if __name__ == '__main__':
   loop = asyncio.get_event_loop()
   loop.run_until_complete(asyncio.wait(tasks))

这一次的控制台打印也和上面一样是1,3,2,4但是它们并不相同,asyncio会通过tasks存放任务,然后在执行func1函数的时候遇到IO耗时操作时,它不会去等待而是去调用func2的函数,然后在func2执行到IO阻塞时,又会继续的去执行tasks中的其他任务(这里只有2个任务func1、func2),所以在执行到1、3的时候会等待一会,并继续执行func1函数的操作然后又执行完func2函数得到相应的结果。

示例如下:
python asyncio异步编程_第2张图片
那么对于当前示例代码而言,并不能突出asyncio异步的强大,但是对于发送网络IO请求的操作来说,asyncio异步则发挥了它强大之处,这里我们会再后面的爬虫示例中会用介绍到。

2.4 async & await关键字

如果想使用async & await异步,我们需要在python3.5及之后的版本,对于该关键字其实就是简化版的asyncio模块的操作,本质上没什么区别,只是让我们使用异步更加的方便罢了,而使用方法我们可以通过修改上述asyncio异步的示例代码如下:

import asyncio


async def func1():
   print(1)
   await asyncio.sleep(2)  # 遇到IO耗时操作,自动切换到tasks中的其他任务
   print(2)


async def func2():
   print(3)
   await asyncio.sleep(2)  # 遇到IO耗时操作,自动切换到tasks中的其他任务
   print(4)


tasks = [
   asyncio.ensure_future(func1()),
   asyncio.ensure_future(func2())
]

if __name__ == '__main__':
   loop = asyncio.get_event_loop()
   loop.run_until_complete(asyncio.wait(tasks))

控制台打印结果和asyncio异步一模一样结果为1、3、2、4。

所以综合上述的实现协程的模块最常用的也就2个,python3.4以下版本的使用greenlet模块,而python3.4以上的则使用async & await关键字

3.异步编程

3.1 协程的意义

对于协程而言,其可以通过一个线程在多个上下文中进行来回切换执行,而切换的意义在于:

  1. IO型的操作,利用协程在IO等待时间就去切换执行其他任务
  2. 当IO操作结束后再自动回调,那么就会大大节省资源并提供性能,从而实现异步编程(不等待任务结束就可以去执行其他代码

那么我们这里就通过一个爬虫的案例来区分一下同步、异步编程直接的区别。

首先我们需要安装requests模块pip3 install requests

同步爬虫编程代码如下:

"""
下载图片使用第三方模块requests,请提前安装:pip3 install requests
"""
import requests
def download_image(url):
   print("开始下载:",url)
   # 发送网络请求,下载图片
   response = requests.get(url)
   print("下载完成")
   # 图片保存到本地文件
   file_name = url.rsplit('/')[-1]
   with open(file_name, mode='wb') as file_object:
       file_object.write(response.content)
if __name__ == '__main__':
   url_list = [
       'https://wx-1304867879.cos.ap-guangzhou.myqcloud.com/1o70bjkm1624113947958.jpg',
       'https://wx-1304867879.cos.ap-guangzhou.myqcloud.com/1ccb7469d76d17be.jpg',
	'https://wx-1304867879.cos.ap-guangzhou.myqcloud.com/0wymk86w1625639377771.jpg'
   ]
   for item in url_list:
       download_image(item)

此时执行如图所示:

python asyncio异步编程_第3张图片
可以看到代码的执行是一步一步的执行(从开始下载到下载完成到之后迭代下一个同步进行)的。

因为asyncio并不支持网络请求,所以下载图片使用第三方模块aiohttp,请提前安装:pip3 install aiohttp

基于协程的异步编程实现代码如下:

#!/usr/bin/env python
# -*- coding:utf-8 -*-
import aiohttp
import asyncio
async def fetch(session, url):
   print("发送请求:", url)
   async with session.get(url, verify_ssl=False) as response:
       content = await response.content.read()
       file_name = url.rsplit('/')[-1]
       with open(file_name, mode='wb') as file_object:
           file_object.write(content)
           print("下载完成")
async def main():
   async with aiohttp.ClientSession() as session:
       url_list = [
       'https://wx-1304867879.cos.ap-guangzhou.myqcloud.com/1o70bjkm1624113947958.jpg',
       'https://wx-1304867879.cos.ap-guangzhou.myqcloud.com/1ccb7469d76d17be.jpg',
	'https://wx-1304867879.cos.ap-guangzhou.myqcloud.com/0wymk86w1625639377771.jpg'
   ]
       tasks = [asyncio.create_task(fetch(session, url)) for url in url_list]
       await asyncio.wait(tasks)
if __name__ == '__main__':
   asyncio.run(main())

此时执行如图所示:
python asyncio异步编程_第4张图片

可以看到通过异步发起网络请求的方式是在发送请求之后的下载过程中,不会进行等待,而是继续执行其下一个任务,然后以此类推到任务结束。

那么通过上述两种的执行对比之后会发现,基于协程的异步编程 要比 同步编程的效率高了很多。因为:

  • 同步编程,按照顺序逐一排队执行,如果图片下载时间为2分钟,那么全部执行完则需要6分钟。
  • 异步编程,几乎同时发出了3个下载任务的请求(遇到IO请求自动切换去发送其他任务请求),如果图片下载时间为2分钟,那么全部执行完毕也大概需要2分钟左右就可以了。

3.2 事件循环

事件循环,可以把他当做是一个while循环,这个while循环在周期性的运行并执行一些任务,在特定条件下终止循环。

在编写程序时候可以通过如下代码来获取和创建事件循环。

import asyncio
# 去生成或获取一个事件循环
loop = asyncio.get_event_loop()
# 将任务放到任务列表
loop.run_until_complete(任务)

3.3 快速上手

对于协程函数而言,在py3.5以上版本定义函数的时候会通过async def 函数名,那么该如何去调用该协程函数呢?我们需要通过将该函数通过任务的方式,放入到事件循环当中,然后在通过事件循环去执行该任务(对于事件循环而言可以有多个任务)。

所以实现代码方法如下:

import asyncio


async def func():
   print("啦啦啦啦啦")


# 此时函数内部代码是不会执行的
result = func()

"""执行事件循环"""
# py3.7以下的写法
# loop = asyncio.get_event_loop()
# loop.run_until_complete(result)

# py3.7以上的写法
asyncio.run(result)

那么对于await(等待)的用法 = await + 可等待的对象 (协程对象、Future、Task对象)统称为IO等待,而其搭配async使用后就会在等待的时候将事件循环的任务依次执行。

所以对于一个任务而言,是无法演示当IO等待的时候切换到其他任务的效果。

所以这里我们介绍一个可以创建多个任务对象,需要使用Task对象来实现。

Tasks用于并发调度协程,通过asyncio.create_task(协程对象)并且需要在py3.7以上版本,低于版本可用asyncio.ensure_future() 函数的方式创建Task对象,这样可以让协程加入事件循环中等待被调度执行。除了使用 asyncio.create_task() 函数以外,还可以用低层级的 loop.create_task() 或 ensure_future() 函数。不建议手动实例化 Task 对象。

本质上是将协程对象封装成task对象,并将协程立即加入事件循环,同时追踪协程的状态。

那么我们就可以配合前面的async、await、事件循环、task组合实现asyncio异步操作。

代码如下:

import asyncio


async def func():
    print(1)
    await asyncio.sleep(2)
    print(2)
    return "返回值"


async def main():
    print("main开始")
    task1 = asyncio.create_task(func())
    task2 = asyncio.create_task(func())
    print("main结束")
    # 当执行某协程遇到IO操作时,会自动化切换执行其他任务。
    # 此处的await是等待相对应的协程全都执行完毕并获取结果
    ret1 = await task1
    ret2 = await task2
    print(ret1, ret2)


asyncio.run(main())

可以看到我们将main函数放入了事件循环中,之后创建协程(async def func函数),将协程封装到一个Task对象中并立即添加到事件循环的任务列表中,等待事件循环去执行(默认是就绪状态)。然后通过await 进行IO等待(等待过程中会执行下一个任务),最后打印结果如图所示。

python asyncio异步编程_第5张图片
在上述代码中,我们是一个一个的将协程封装到一个Task对象中并添加到事件循环的任务列表的,对于asyncio异步来说,还有一个更为简洁的方法,就是把每一个创建好的任务放到一个列表中,然后通过asyncio.wait函数(该函数源码内部会对列表中的每个协程执行ensure_future从而封装为Task对象,所以在存放时可直接添加函数或使用已封装的Task对象(前提是事件循环是否已经创建))存入列表参数,并可以设置等待时间timeout(即等待任务时间,默认为None),且最后返回两个参数done、pending(完成的协程返回值写入到done中,未完成则写到pending中)。

代码如下:

import asyncio


async def func():
    print(1)
    await asyncio.sleep(2)
    print(2)
    return "返回值"


async def main():
    print("main开始")
    # 创建协程,将协程封装到Task对象中并添加到事件循环的任务列表中,等待事件循环去执行(默认是就绪状态)。
    # 在调用
    task_list = [
        asyncio.create_task(func()),
        asyncio.create_task(func())
    ]
    print("main结束")
    # 当执行某协程遇到IO操作时,会自动化切换执行其他任务。
    # 此处的await是等待所有协程执行完毕,并将所有协程的返回值保存到done
    # 如果设置了timeout值,则意味着此处最多等待的秒,完成的协程返回值写入到done中,未完成则写到pending中。
    done, pending = await asyncio.wait(task_list, timeout=None)
    print(done, pending)


if __name__ == '__main__':
    asyncio.run(main())

最后返回结果是一致的。

在syncio中的Future(task是其派生类)对象是一个相对更偏向底层的可对象,通常我们不会直接用到这个对象,而是直接使用Task对象来完成任务的并和状态的追踪。

那么对于Future对象的使用如下:

import asyncio
async def set_after(fut):
    await asyncio.sleep(2)
    fut.set_result("666")
async def main():
    # 获取当前事件循环
    loop = asyncio.get_running_loop()
    # 创建一个任务(Future对象),没绑定任何行为,则这个任务永远不知道什么时候结束。
    fut = loop.create_future()
    # 创建一个任务(Task对象),绑定了set_after函数,函数内部在2s之后,会给fut赋值。
    # 即手动设置future任务的最终结果,那么fut就可以结束了。
    await loop.create_task(set_after(fut))
    # 等待 Future对象获取 最终结果,否则一直等下去
    data = await fut
    print(data)
asyncio.run(main())

此时打印结果为666。

Future对象本身函数进行绑定,所以想要让事件循环获取Future的结果,则需要手动设置。而Task对象继承了Future对象,其实就对Future进行扩展,他可以实现在对应绑定的函数执行完成之后,自动执行set_result,从而实现自动结束。

在前面的案例和应用中,我们都是用通过协程实现异步的操作,但是在一些情况下,如果我们在用的模块中并不支持通过协程进行异步,比如之前讲到过的网络请求,那么我们该怎么实现异步操作呢?

在Python的concurrent.futures模块中也有一个Future对象,这个对象是基于线程池和进程池实现异步操作时使用的对象。

实现方法如下:

import time
from concurrent.futures import Future
from concurrent.futures.thread import ThreadPoolExecutor
from concurrent.futures.process import ProcessPoolExecutor
def func(value):
    time.sleep(1)
    print(value)
pool = ThreadPoolExecutor(max_workers=5)
# 或 pool = ProcessPoolExecutor(max_workers=5)
for i in range(10):
    fut = pool.submit(func, i)
    print(fut)

两个Future对象是不同的,他们是为不同的应用场景而设计,例如:concurrent.futures.Future不支持await语法 等。

但如果 协程的异步进程池/线程池的异步 混搭时,那么就会用到此功能了,代码如下:

import time
import asyncio
import concurrent.futures
def func1():
    # 某个耗时操作
    time.sleep(2)
    return "SB"
async def main():
    loop = asyncio.get_running_loop()
    # 1. Run in the default loop's executor ( 默认ThreadPoolExecutor )
    # 第一步:内部会先调用 ThreadPoolExecutor 的 submit 方法去线程池中申请一个线程去执行func1函数,并返回一个concurrent.futures.Future对象
    # 第二步:调用asyncio.wrap_future将concurrent.futures.Future对象包装为asycio.Future对象。
    # 因为concurrent.futures.Future对象不支持await语法,所以需要包装为 asycio.Future对象 才能使用。
    fut = loop.run_in_executor(None, func1)
    result = await fut
    print('default thread pool', result)
    # 2. Run in a custom thread pool:
    # with concurrent.futures.ThreadPoolExecutor() as pool:
    #     result = await loop.run_in_executor(
    #         pool, func1)
    #     print('custom thread pool', result)
    # 3. Run in a custom process pool:
    # with concurrent.futures.ProcessPoolExecutor() as pool:
    #     result = await loop.run_in_executor(
    #         pool, func1)
    #     print('custom process pool', result)
asyncio.run(main())

基于之前的一个爬虫案例用到了aiohttp模块,因为内部并不支持协程异步,但是当我们学会了进程池/线程池的异步,那么事情就变得简单了。

爬取图片代码改进如下:

import asyncio
import requests


async def download_image(url):
    # 发送网络请求,下载图片(遇到网络下载图片的IO请求,自动化切换到其他任务)
    print("开始下载:", url)
    loop = asyncio.get_event_loop()
    # requests模块默认不支持异步操作,所以就使用线程池来配合实现了。
    future = loop.run_in_executor(None, requests.get, url)
    response = await future
    print('下载完成')
    # 图片保存到本地文件
    file_name = url.rsplit('/')[-1]
    with open(file_name, mode='wb') as file_object:
        file_object.write(response.content)


if __name__ == '__main__':
    url_list = [
        'https://wx-1304867879.cos.ap-guangzhou.myqcloud.com/1o70bjkm1624113947958.jpg',
        'https://wx-1304867879.cos.ap-guangzhou.myqcloud.com/1ccb7469d76d17be.jpg',
        'https://wx-1304867879.cos.ap-guangzhou.myqcloud.com/0wymk86w1625639377771.jpg'
    ]
    tasks = [download_image(url) for url in url_list]
    loop = asyncio.get_event_loop()
    loop.run_until_complete(asyncio.wait(tasks))

此时返回的结果和上述爬虫案例是一样的,不过是通过了asyncio的进程池/线程池的异步操作。

3.4 异步迭代器

实现了 aiter() 和 anext() 方法的对象。anext 必须返回一个 awaitable 对象。async for 会处理异步迭代器的 anext() 方法所返回的可等待对象,直到其引发一个 StopAsyncIteration 异常。

而异步可迭代对象可在 async for 语句中被使用的对象。必须通过它的 aiter() 方法返回一个 asynchronous iterator。

实现代码如下:

import asyncio
class Reader(object):
    """ 自定义异步迭代器(同时也是异步可迭代对象) """
    def __init__(self):
        self.count = 0
    async def readline(self):
        # await asyncio.sleep(1)
        self.count += 1
        if self.count == 100:
            return None
        return self.count
    def __aiter__(self):
        return self
    async def __anext__(self):
        val = await self.readline()
        if val == None:
            raise StopAsyncIteration
        return val
async def func():
    # 创建异步可迭代对象
    async_iter = Reader()
    # async for 必须要放在async def函数内,否则语法错误。
    async for item in async_iter:
        print(item)
asyncio.run(func())

异步迭代器其实没什么太大的作用,只是支持了async for语法而已。

3.5 异步上下文管理

此种对象通过定义 aenter() 和 aexit() 方法来对 async with 语句中的环境进行控制。

import asyncio
class AsyncContextManager:
    def __init__(self):
        self.conn = None
    async def do_something(self):
        # 异步操作数据库
        return 666
    async def __aenter__(self):
        # 异步链接数据库
        self.conn = await asyncio.sleep(1)
        return self
    async def __aexit__(self, exc_type, exc, tb):
        # 异步关闭数据库链接
        await asyncio.sleep(1)
async def func():
    async with AsyncContextManager() as f:
        result = await f.do_something()
        print(result)
asyncio.run(func())

这个异步的上下文管理器还是比较有用的,平时在开发过程中 打开、处理、关闭 操作时,就可以用这种方式来处理。

4.异步实战案例

摘自:https://pythonav.com/wiki/detail/6/91/

4.1 uvloop

Python标准库中提供了asyncio模块,用于支持基于协程的异步编程。

uvloop是 asyncio 中的事件循环的替代方案,替换后可以使得asyncio性能提高。事实上,uvloop要比nodejs、gevent等其他python异步框架至少要快2倍,性能可以比肩Go语言。

安装uvloop

pip3 install uvloop

在项目中想要使用uvloop替换asyncio的事件循环也非常简单,只要在代码中这么做就行。

import asyncio
import uvloop
asyncio.set_event_loop_policy(uvloop.EventLoopPolicy())
# 编写asyncio的代码,与之前写的代码一致。
# 内部的事件循环自动化会变为uvloop
asyncio.run(...)

知名的asgi uvicorn内部就是使用的uvloop的事件循环。

4.2 异步redis

当通过python去操作redis时,链接、设置值、获取值 这些都涉及网络IO请求,使用asycio异步的方式可以在IO等待时去做一些其他任务,从而提升性能。

安装Python异步操作redis模块

pip3 install aioredis==1.3.1

因为我参考沛齐老师的异步操作redis是2020年之前的,现在已经更新至2.0.0版本,这里的异步redis我们具体是学习思路,后面的fastapi会有两个版本的示例

示例1:异步操作redis。

#!/usr/bin/env python
# -*- coding:utf-8 -*-
import asyncio
import aioredis
async def execute(address, password):
    print("开始执行", address)
    # 网络IO操作:创建redis连接
    redis = await aioredis.create_redis(address, password=password)
    # 网络IO操作:在redis中设置哈希值car,内部在设三个键值对,即: redis = { car:{key1:1,key2:2,key3:3}}
    await redis.hmset_dict('car', key1=1, key2=2, key3=3)
    # 网络IO操作:去redis中获取值
    result = await redis.hgetall('car', encoding='utf-8')
    print(result)
    redis.close()
    # 网络IO操作:关闭redis连接
    await redis.wait_closed()
    print("结束", address)
asyncio.run(execute('redis://47.93.4.198:6379', "密码"))

示例2:连接多个redis做操作(遇到IO会切换其他任务,提供了性能

import asyncio
import aioredis
async def execute(address, password):
    print("开始执行", address)
    # 网络IO操作:先去连接 47.93.4.197:6379,遇到IO则自动切换任务,去连接47.93.4.198:6379
    redis = await aioredis.create_redis_pool(address, password=password)
    # 网络IO操作:遇到IO会自动切换任务
    await redis.hmset_dict('car', key1=1, key2=2, key3=3)
    # 网络IO操作:遇到IO会自动切换任务
    result = await redis.hgetall('car', encoding='utf-8')
    print(result)
    redis.close()
    # 网络IO操作:遇到IO会自动切换任务
    await redis.wait_closed()
    print("结束", address)
task_list = [
    execute('redis://47.93.4.197:6379', "密码"),
    execute('redis://47.93.4.198:6379', "密码")
]
asyncio.run(asyncio.wait(task_list))

更多redis操作参考aioredis官网:https://aioredis.readthedocs.io/en/latest/

4.3 异步MySQL

当通过python去操作MySQL时,连接、执行SQL、关闭都涉及网络IO请求,使用asycio异步的方式可以在IO等待时去做一些其他任务,从而提升性能。

安装Python异步操作aiomysql模块

pip3 install aiomysql

示例1:

import asyncio
import aiomysql
async def execute():
    # 网络IO操作:连接MySQL
    conn = await aiomysql.connect(host='127.0.0.1', port=3306, user='root', password='密码', db='mysql', )
    # 网络IO操作:创建CURSOR
    cur = await conn.cursor()
    # 网络IO操作:执行SQL
    await cur.execute("SELECT Host,User FROM user")
    # 网络IO操作:获取SQL结果
    result = await cur.fetchall()
    print(result)
    # 网络IO操作:关闭链接
    await cur.close()
    conn.close()
asyncio.run(execute())

示例2:

#!/usr/bin/env python
# -*- coding:utf-8 -*-
import asyncio
import aiomysql
async def execute(host, password):
    print("开始", host)
    # 网络IO操作:先去连接 47.93.40.197,遇到IO则自动切换任务,去连接47.93.40.198:6379
    conn = await aiomysql.connect(host=host, port=3306, user='root', password=password, db='mysql')
    # 网络IO操作:遇到IO会自动切换任务
    cur = await conn.cursor()
    # 网络IO操作:遇到IO会自动切换任务
    await cur.execute("SELECT Host,User FROM user")
    # 网络IO操作:遇到IO会自动切换任务
    result = await cur.fetchall()
    print(result)
    # 网络IO操作:遇到IO会自动切换任务
    await cur.close()
    conn.close()
    print("结束", host)
task_list = [
    execute('47.93.40.197', "密码"),
    execute('47.93.40.197', "密码")
]
asyncio.run(asyncio.wait(task_list))

更多mysql操作参考aiomysql官网:https://aiomysql.readthedocs.io/en/latest/

4.4 FastAPI框架

FastAPI是一款用于构建API的高性能web框架,框架基于Python3.6+的 type hints搭建。

接下里的异步示例以FastAPIuvicorn来讲解(uvicorn是一个支持异步的asgi)。

首先安装FastAPI web 框架。

pip3 install fastapi

安装uvicorn,本质上为web提供socket server的支持的asgi(一般支持异步称asgi、不支持异步称wsgi)

pip3 install uvicorn

基于aioredis版本1.3.1如下(这里我们创建的py文件为fast):

#!/usr/bin/env python
# -*- coding:utf-8 -*-
import asyncio
import uvicorn
import aioredis
from aioredis import Redis
from fastapi import FastAPI

app = FastAPI()
REDIS_POOL = aioredis.ConnectionsPool('redis://127.0.0.1:6379', password="mi@o2004", minsize=1, maxsize=10)


@app.get("/")
def index():
    """ 普通操作接口 """
    return {"message": "Hello World"}


@app.get("/red")
async def red():
    """ 异步操作接口 """
    print("请求来了")
    await asyncio.sleep(3)
    # 连接池获取一个连接
    conn = await REDIS_POOL.acquire()
    redis = Redis(conn)
    # 设置值
    await redis.hmset_dict('var', key1=1, key2=2, key3=3)
    # 读取值
    result = await redis.hgetall('var', encoding='utf-8')
    print(result)
    # 连接归还连接池
    REDIS_POOL.release(conn)
    return result


if __name__ == '__main__':
    uvicorn.run("fast:app", host="127.0.0.1", port=5000, log_level="info")

基于aioredis版本2.0.0如下(这里我们创建的py文件为fast2):

import asyncio
import uvicorn
import aioredis
from fastapi import FastAPI

app = FastAPI()
REDIS_POOL = aioredis.from_url('redis://:mi@[email protected]:6379')


@app.get("/")
def index():
    """ 普通操作接口 """
    return {"message": "Hello World"}


@app.get("/red")
async def red():
    """ 异步操作接口 """
    print("请求来了")
    await asyncio.sleep(3)
    # 设置值
    await REDIS_POOL.hmset('val', {'key1': 1, 'ke2': 2, 'ke3': 3})
    result = await REDIS_POOL.hgetall('val')
    print(result)
    return result


if __name__ == '__main__':
    uvicorn.run("fast2:app", host="127.0.0.1", port=5000, log_level="info")

在有多个用户并发请求的情况下,异步方式来编写的接口可以在IO等待过程中去处理其他的请求,提供性能。

例如:同时有两个用户并发来向接口 http://127.0.0.1:5000/red 发送请求,服务端只有一个线程,同一时刻只有一个请求被处理。 异步处理可以提供并发是因为:当视图函数在处理第一个请求时,第二个请求此时是等待被处理的状态,当第一个请求遇到IO等待时,会自动切换去接收并处理第二个请求,当遇到IO时自动化切换至其他请求,一旦有请求IO执行完毕,则会再次回到指定请求向下继续执行其功能代码。

基于上下文管理,来实现自动化管理的案例(这里只有1.3.1之前的版本,之后的2.0.0版本已经支持asyncio):

#!/usr/bin/env python
# -*- coding:utf-8 -*-
import asyncio
import uvicorn
import aioredis
from aioredis import Redis
from fastapi import FastAPI
app = FastAPI()
REDIS_POOL = aioredis.ConnectionsPool('redis://127.0.0.1:6379', password="root123", minsize=1, maxsize=10)
@app.get("/")
def index():
    """ 普通操作接口 """
    return {"message": "Hello World"}
@app.get("/red")
async def red():
    """ 异步操作接口 """
    print("请求来了")
    async with REDIS_POOL.get() as conn:
        redis = Redis(conn)
        # 设置值
        await redis.hmset_dict('car', key1=1, key2=2, key3=3)
        # 读取值
        result = await redis.hgetall('car', encoding='utf-8')
        print(result)
    return result
if __name__ == '__main__':
    uvicorn.run("fast:app", host="127.0.0.1", port=5000, log_level="info")

通过mysql的方式:

#!/usr/bin/env python
# -*- coding:utf-8 -*-
import asyncio
import uvicorn
from fastapi import FastAPI
import aiomysql
app = FastAPI()
# 创建数据库连接池
pool = aiomysql.Pool(host='127.0.0.1', port=3306, user='root', password='密码', db='mysql',
                     minsize=1, maxsize=10, echo=False, pool_recycle=-1, loop=asyncio.get_event_loop())
@app.get("/red")
async def red():
    """ 异步操作接口 """
    # 去数据库连接池申请链接
    async with pool.acquire() as conn:
        async with conn.cursor() as cur:
            # 网络IO操作:执行SQL
            await cur.execute("SELECT Host,User FROM user")
            # 网络IO操作:获取SQL结果
            result = await cur.fetchall()
            print(result)
            # 网络IO操作:关闭链接
    return {"result": "ok"}
if __name__ == '__main__':
    uvicorn.run("fast:app", host="127.0.0.1", port=5000, log_level="info")

你可能感兴趣的:(python之路,python,asyncio,fastapi)