协程&异步编程

协程,也可以被称为微线程,是一种用户态内的上下文切换技术。简而言之,其实就是通过一个线程实现代码块相互切换执行

协程一般应用在有IO操作的程序中,因为协程可以利用IO等待的时间去执行一些其他的代码,从而提升代码执行效率。

async

事件循环

事件循环,可以理解为while循环,在周期性的执行一些任务,在任务列表为空时终止循环。

# 伪代码

任务列表 = [ 任务1, 任务2, 任务3,... ]

while True:
    可执行的任务列表,已完成的任务列表 = 去任务列表中检查所有的任务,将'可执行'和'已完成'的任务返回
    
    for 就绪任务 in 已准备就绪的任务列表:
        执行已就绪的任务
        
    for 已完成的任务 in 已完成的任务列表:
        在任务列表中移除 已完成的任务

	如果 任务列表 中的任务都已完成,则终止循环

协程函数,定义形式为 async def 的函数。

协程对象,调用 协程函数 所返回的对象。

执行协程函数,需要 事件循环协程对象 配合实现。

import asyncio


# 定义一个协程函数
async def func():
    print('我是tasks')
    
# 调用协程函数,返回一个协程对象  注意:函数内部代码不会执行
result = func()

#方式1
# loop = asyncio.get_event_loop()   # 创建一个事件循环
# loop.run_until_complete(result)   # 将协程当做任务提交到事件循环的任务列表中,协程执行完成之后终止。

# 方式2
asyncio.run(result)   # asyncio.run 函数在 Python 3.7 中加入 asyncio 模块

awit

await是一个只能在协程函数中使用的关键字,用于遇到IO操作时挂起 当前协程(任务),当前协程(任务)挂起过程中 事件循环可以去执行其他的协程(任务),当前协程IO处理完成时,可以再次切换回来执行await之后的代码

示例

import asyncio

# await后面可以加 协程对象 task对象 future对象
async def download():
    print('start')
    await asyncio.sleep(2)
    print('end')
    return 'ok'

async def func():
    print('我是tasks')
    res = await download()
    print('结束',res)
    res2 = await download()
    print('结束', res2)

asyncio.run(func())

上述的示例只创建了一个任务,所以无法切换到其他任务

在程序想要创建多个任务对象,需要使用Task对象来实现。

Task对象

Tasks用于并发调度协程,通过asyncio.create_task(协程对象)的方式创建Task对象,可以让协程加入事件循环中等待被调度执行。

task 添加任务放到事件循环

示例1

import asyncio

async def download():
    print('start')
    await asyncio.sleep(2)
    print('end')
    return 'ok'

async def func():
    print('main')
    # 创建协程,将协程封装到一个Task对象中并立即添加到事件循环的任务列表中,等待事件循环去执行(默认是就绪状态)。
    task1 = asyncio.create_task(download())
    task2 = asyncio.create_task(download())
    print('end')

    ret1 = await task1
    ret2 = await task2
    print(ret1,ret2)

asyncio.run(func())

示例2

import asyncio

async def download():
    print('start')
    await asyncio.sleep(2)
    print('end')
    return 'ok'
    
# 常规写法
async def func2():
    print('main')
    task_list = [
        asyncio.create_task(download(),name='t1'),
        asyncio.create_task(download(),name='t2'),
    ]
    print('end')
    done,_ = await asyncio.wait(task_list)
    print(done)

asyncio.run(func2())

注意:asyncio.wait 源码内部会对列表中的每个协程执行ensure_future从而封装为Task对象,所以在和wait配合使用时task_list的值为[func(),func()] 也是可以的。

示例3

import asyncio

async def download():
    print('start')
    await asyncio.sleep(2)
    print('end')
    return 'ok'

# 放到外部执行
def main():
    task_list = [
            download(),
            download()
        ]
    # 错误:task_list = [ asyncio.create_task(download()), asyncio.create_task(download()) ]  
    # 此处不能直接 asyncio.create_task,因为将Task立即加入到事件循环的任务列表,
    # 但此时事件循环还未创建,所以会报错。
    
    done,_ = asyncio.run(asyncio.wait(task_list))
    print(done)
    # 使用asyncio.wait将列表封装为一个协程,并调用asyncio.run实现执行两个协程
    # asyncio.wait内部会对列表中的每个协程执行ensure_future,封装为Task对象。

main()

asyncio.Future对象

task 继承 funture ,task对象内部await结果的处理基于funture对象来的

import asyncio

async def main():
    # 获取当前事件循环
    loop = asyncio.get_running_loop()
    # # 创建一个任务(Future对象),这个任务什么都不干。
    fut = loop.create_future()
    # 等待任务最终结果(Future对象),没有结果则会一直等下去。
    await fut

asyncio.run(main())

futures.Future对象

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

import asyncio
import time
from concurrent.futures.thread import ThreadPoolExecutor

def func():
    time.sleep(1)
    return 123

def main():
    pool = ThreadPoolExecutor(max_workers=5)
    fut = pool.submit(func)
    print(fut)

main()

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

在Python提供了一个将futures.Future 对象包装成asyncio.Future对象的函数

两个Future混合使用

import asyncio
import time
from concurrent.futures.thread import ThreadPoolExecutor

def func():
    time.sleep(1)
    return 123

# 将线程池的future对象转换为async的future对象
async def main2():
    loop = asyncio.get_running_loop()
    #获取当前的事件循环

    #1.为 None 默认生成 ThreadPoolExecutor,任务加入线程池里运行,返回future
    fut = loop.run_in_executor(None,func)
    result = await fut
    print(result)
    # 2.
    # with ThreadPoolExecutor() as pool:
    #     fut = await loop.run_in_executor(pool,func)
    #     print(fut)

asyncio.run(main2())

应用场景

# 异步与非异步模块的案例
import asyncio
import requests

async def download(url):
    print('开始下载',url)
    loop = asyncio.get_event_loop()
    #requests模块不支持异步操作,所以通过线程池配合
    fut = loop.run_in_executor(None,requests.get,url)
    response = await fut
    print('下载完成')
    file_name = url.rsplit('/')[-1]+'.jpg'
    with open(file_name,'wb') as f:
        f.write(response.content)
def main3():
    url_list = [
        "https://pic.quanjing.com/jp/1w/QJ6144539261.jpg@!350h",
        "https://pic.quanjing.com/lv/8f/QJ6625036082.jpg@!350h",
        "https://pic.quanjing.com/12/90/QJ8124912494.jpg@!350h"
    ]
    tasks = [download(url) for url in url_list]
    asyncio.run(asyncio.wait(tasks))

main3()
# 注:此案例耗费了线程池在等待任务结束,推荐使用aiohttp

异步迭代器

实现了 __aiter__()__anext__() 方法的对象,__anext__ 必须返回一个 awaitable 对象

async for 会处理异步迭代器的 __anext__() 方法所返回的可等待对象,直到其引发一个 StopAsyncIteration 异常

示例

import asyncio

class AsyncIterator():
    def __init__(self):
        self.counter = 0

    def __aiter__(self):
        return self

    async def __anext__(self):
        if self.counter >= 10:
            raise StopAsyncIteration
        self.counter += 1
        return self.counter
    
async def func():
    iter = AsyncIterator()
    async for i in iter:
        print(i)

asyncio.run(func())

异步上下文管理器

定义 __aenter__()__aexit__() 方法来对 async with 语句中的环境进行控制

示例

import asyncio

class AsyncContextManager:
    def __init__(self,conn):
        self.conn = conn

    async def __aenter__(self):
        # 异步连接数据库
        print('连接')
        self.conn = await asyncio.sleep(2) #修改为数据库连接
        return self

    async def __aexit__(self, exc_type, exc, tb):
        # 异步关闭数据库连接
        print('关闭')
        await asyncio.sleep(2)  # 修改为关闭数据库连接

    async def do_something(self):
        #操作数据库
        await asyncio.sleep(2)
        return 123

async def func2():
    async with AsyncContextManager('conn') as f:
        result = await f.do_something()
        print(result)

asyncio.run(func2())

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

uvloop

uvloop是 asyncio 中的事件循环的替代方案,替换后可以使得asyncio性能提高

示例

#pip3 install uvloop

import asyncio
import uvloop

# uvloop是asyncio的事件循环的代替方案. 事件循环>默认asyncio的事件循环
asyncio.set_event_loop_policy(uvloop.EventLoopPolicy())

# 与之前的一样
#内部的事件循环自动转化为uvloop
asyncio.run(...)
posted @ 2024-03-28 18:38  嘚惹  阅读(18)  评论(0编辑  收藏  举报