python协程专题

定义和概念

协程coroutine

又叫做微线程,在统一线程里,不同子程序可以中断执行其他子程序。中断后,可以从中断的地方继续执行。协程拥有自己的寄存器上下文和栈。用async定义的一个函数,调用时候不会立即执行,而是会返回一个协程对象。python3.7之前协程对象需要注册到事件循环,由事件循环进行调用。python3.7之后协程对象直接使用async.run运行。

event_loop事件循环

async中开启一个无限的事件循环,asyncio会自动在满足条件时调用协程对象,需要将协程对象注册到该事件循环上即可。python3.7之后不建议显式使用事件循环。

task对象

一个协程对象就是一个原生可以挂起的函数,任务是对协程的进一步封装,包含任务的各种状态。 task对象是future对象的子类,将coroutine和future封装在一起,同时也有异常处理等功能。(future对象代表没有执行任务的结果)

async关键字

python3.5之后用于定义协程的关键字,async def定义协程。同时也可以定义异步迭代器和异步上下文管理器
await关键字:作用1、用于挂起协程,await后面跟的是耗时操作,表示此处将调度权释放给事件循环。2、用于返回协程执行结果 ** 注意:1、await只能修饰coroutine和task对象。2、await语句只能位于协程内部,不能位于同步程序中。3、await修饰coroutine会造成阻塞,所以更建议修饰task对象**

基本协程调用

python3.5-3.6 旧版调用方式

1、定义协程对象:async def hello(x)…
2、事件循环: loop = asyncio.get_event_loop() #定义事件循环
3、将协程转为task:asyncio.ensure_future(hello(i)) 或者asyncio.create_task(hello(i))
4、将task放入事件循环对象中(以下两句等价)
loop.run_until_complete(asyncio.gather(*tasks)
loop.run_until_complete(asyncio.wait(tasks))
5、结果通过Task的result()方法获取

python3.7以上调用方式:

1、定义协程对象:async def hello(x)…
2、定义外部协程对象 async def main() 内部包含1(并且把1包装成task)
3、asyncio.run(mian)
4、结果通过await 获取

import time
import asyncio
import random

sentences = [
    '驿外断桥边,寂寞开无主,已是黄昏独自愁',
    '十年生死两茫茫',
    '千里江陵一日还',
    '两岸猿声啼不住,轻舟已过万重山'
]

async def work(index):
    print('start async {}'.format(index))
    sentence = sentences[index]
    for s in sentence:
        await asyncio.sleep(0.3)
    return sentence
#python3.6
tasks = [asyncio.ensure_future(work(index)) for index in range(3)]
loop = asyncio.get_event_loop()
loop.run_until_complete(asyncio.wait(tasks)) #如果是任务list,则使用wait方法,单个任务可以直接run_until_complete
for t in tasks:
    print(t.result())

#python3.7
# async def main():
#     tasks = [asyncio.create_task(work(index)) for index in range(3)]
#     await asyncio.wait(tasks)
#     for t in tasks:
#         print(t.result())
#     print('end')
# asyncio.run(main()) #aysncio.run方法接收并执行一个协程(coroutine)对象,而非Task或Future对象

函数说明

async.run()

必须传入一个协程,会自动把这个协程包装task并启动事件循环。多个协程可以都包装在一个最外层的协程中,使用async.run启动。async.run是python3.7新的协程启动方式。对比asyncio.get_event_loop() + loop.run_until_complete() 方式,可以不用管事件循环的启动和释放。例子见上【python3.7以上调用方式】

asyncio.create_task()

将协程包装成task对象,其功能和asyncio.ensure_future()一样。python3.7之后建议使用asyncio.create_task()
例子见上【python3.7以上调用方式】

asyncio.get_running_loop() 和 loop.run_in_executor()

两者配合可以把一个同步函数包装成一个协程加入线程池,从而加入事件循环,同步函数因为会单独开线程,所以不会阻塞事件循环,示例如下:

import asyncio
import concurrent.futures
import time

def blocking_function(seconds):
    print(f"Starting blocking function for {seconds} seconds.")
    time.sleep(seconds)
    return f"Done after {seconds} seconds."

async def main():
    # 获取当前事件循环
    loop = asyncio.get_running_loop()
    # 创建线程池执行器
    with concurrent.futures.ThreadPoolExecutor() as executor:
        # 使用run_in_executor异步执行阻塞操作
        task1 = loop.run_in_executor(executor, blocking_function, 2)
        task2 = loop.run_in_executor(executor, blocking_function, 4)
        # 等待两个任务完成
        result1 = await task1
        result2 = await task2
        print(result1)
        print(result2)
asyncio.run(main())

asyncio.wait()

async def wait(fs, *, timeout=None, return_when=ALL_COMPLETED)
其本身也是一个协程,且为耗时操作,所以必须用await修饰。主要作用是等待一个可迭代对象(通常是一组 Future 或 Task 对象)中的某些任务完成。这个函数提供了更灵活的控制来管理并发执行的异步任务
具体来说,asyncio.wait 允许你指定以下参数:

fs: 一个可迭代对象,包含了你想要等待完成的 Future 或 Task 对象。
timeout: 可选参数,等待的超时时间(秒),如果设置,则在指定时间后不论任务是否完成都会结束等待。
return_when: 决定了在什么条件下返回。它可以是 asyncio.FIRST_COMPLETED(只要有一个任务完成就返回)、asyncio.ALL_COMPLETED(默认值,所有任务都完成才返回)或 asyncio.FIRST_EXCEPTION(只要有一个任务抛出异常就返回)。

使用 asyncio.wait 后,它会返回两个集合并发结果:一个是已完成的任务集合,另一个是未完成的任务集合。这使得你可以根据任务的状态来决定下一步的操作,比如继续等待、取消未完成的任务或者处理已完成任务的结果。

# python3.7
async def main():
    tasks = [asyncio.create_task(work(index)) for index in range(3)]
    done, pending = await asyncio.wait(tasks) # 等待所有任务执行完成,返回结果列表
    for item in done:
        print(item.result())  # 结果是乱序的
    print('end')
asyncio.run(main())

asyncio.gather()

它用于并发执行多个异步操作(协程),并能够收集这些操作的结果。和asyncio.wait()区别是输入需要进行解包,以及返回结果是有序的。
def gather(*coros_or_futures, return_exceptions=False)

coros_or_futures: 如果你有多个协程要执行,可以直接将它们作为参数传递,或者通过解包操作符 * 传递一个包含协程的列表或元组。
return_exceptions(可选): 一个布尔值,默认为 False。如果设置为 True,则在遇到任何协程抛出异常时,不会立即停止执行其他协程,而是将异常作为结果的一部分收集起来。

示例如下

async def main():
    tasks = [asyncio.create_task(work(index)) for index in range(3)]
    res = await asyncio.gather(*tasks) # 等待所有任务执行完成,返回结果列表,比wait方法更简洁,比await更高效
    print(res)
    print('end')
asyncio.run(main())

回调函数

在上面示例里面加上两个步骤,即可在异步函数调用结束后调用回调函数:
1、回调函数定义callback
2、task.add_done_callback(callback)

import asyncio,time
async def hello(x):
    await asyncio.sleep(x)  #time.sleep()是同步方法,不支持异步调用,此处使用asyncio.sleep
    return '暂停了{}s'.format(x), x
def callback(future:asyncio.Task):
    sum = future.result()[-1] + 3
    print(sum)

if __name__ == '__main__':
    time_start = time.time()
    tasks = []
    loop = asyncio.get_event_loop()
    for i in range(3):
        #task = asyncio.create_task(hello(i))
        task = asyncio.ensure_future(hello(i)) #ensure_future 和 create_task 都可以,优先用 ensure_future
        task.add_done_callback(callback)
        tasks.append(task)
    #loop.run_until_complete(asyncio.wait(tasks))
    loop.run_until_complete(asyncio.gather(*tasks)) #gather 和 wait 功能类似, wait接受的是列表,gather直接接受对象
    for task in tasks:
        print(task.result())
    print(time.time() - time_start)

异步迭代器

允许你在异步环境下进行迭代操作。异步迭代器主要通过实现__aiter__()和__anext__()这两个魔法方法来工作,分别用于初始化迭代器和获取下一个异步的迭代项。这使得你能够在协程中使用async for语句遍历异步数据源,比如从网络流式读取数据或者处理异步生成的数据序列,而无需阻塞事件循环。注意:异步迭代器必须在协程中定义,不能出现在同步函数中。 异步迭代器中往往也存在耗时异步操作

import asyncio
class AsyncCounter:
    """
    异步迭代器示例,每隔一段时间生成下一个计数。
    """
    def __init__(self, limit=10, delay=1):
        self.limit = limit
        self.delay = delay
        self.current = 0

    async def __aiter__(self):
        while self.current < self.limit:
            await asyncio.sleep(self.delay)  # 模拟异步延迟
            yield self.current
            self.current += 1

async def async_generator():
    for i in range(5):
        # 模拟异步操作,如网络请求或磁盘I/O,这里使用sleep模拟延迟
        await asyncio.sleep(1)
        yield i

async def main():
    #counter = AsyncCounter(limit=5, delay=2)  # 生成5个数字,每次间隔2秒
    counter = async_generator()
    async for number in counter:
        print(f"Count: {number}")
    
# 运行主函数
asyncio.run(main())

循环读文件

import asyncio
import aiofiles

async def async_file_reader(file_path):
    """
    用于逐行读取文件。
    """
    async with aiofiles.open(file_path, mode='r') as file:
        async for line in file:
            # 模拟异步读取每行的延迟
            await asyncio.sleep(0.1)  # 假设每读一行等待0.1秒
            yield line.strip()

async def main():
    file_path = 'example.txt'  # 假定要读取的文件路径
    async for line in async_file_reader(file_path):
        print(line)

asyncio.run(main())

异步上下文管理器

与同步上下文管理器类似,但是设计用于异步环境中。异步上下文管理器通过定义两个特殊的方法__aenter__和__aexit__来工作,这些方法会在async with语句的入口和出口处被调用,分别用于获取资源和释放资源,同时也能处理在async with块中发生的异常。

import asyncio
class AsyncFile:
    def __init__(self, filename, mode):
        self.filename = filename
        self.mode = mode
        self.file = None


    async def __aenter__(self):
        print(f"Opening file {self.filename}...")
        # 模拟异步打开文件,这里简化处理,实际应用中可能是网络请求等
        await asyncio.sleep(1)
        self.file = open(self.filename, self.mode)
        return self.file


    async def __aexit__(self, exc_type, exc, tb):
        print("Closing file.")
        if self.file:
            self.file.close()
        # 可以在这里根据exc_type等参数处理异常
        # 如果没有异常,这三个参数将为None, None, None


async def read_file_contents(filename):
    async with AsyncFile(filename, 'r') as f:
        contents = await f.read()
        print(contents)

asyncio.run(read_file_contents('example.txt'))

协程和线程对比示例

分别使用协程和线程创建多文件

import asyncio, time

async def write_file(path, content:str, index:int):
    print("now writing file", index)
    with open(path, 'w') as f:
        f.write("this is the {} file: {}".format(index,content))

import threading, time
def write_file2(path, content:str, index:int):
    print("now writing file", index)
    with open(path, 'w') as f:
        f.write("this is the {} file: {}".format(index,content))

if __name__ == '__main__':
    time_start = time.time()
    loop = asyncio.get_event_loop()
    tasks = []
    for i in range(300):
        #task = asyncio.create_task(write_file("test_{}.txt".format(i), "the data is {}".format(i), i))
        task = asyncio.ensure_future(write_file("tmp/test_{}.txt".format(i), "the data is {}".format(i),i))
        tasks.append(task)
    loop.run_until_complete(asyncio.wait(tasks))
    print("async time cost:", time.time() - time_start)
    
    time_start = time.time()
    threads = []
    for i in range(300):
        threads.append(threading.Thread(target=write_file2,   args=("tmp1/test_{}.txt".format(i), "the data is {}".format(i),i)))
    [t.start() for t in threads]
    [t.join() for t in threads]
    print("thread time cost:", time.time() - time_start)

协程速度和多线程接近。

  • 27
    点赞
  • 28
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值