python协程asyncio使用

协程

协程 (corountine):又称微线程。

asyncio,在单线程利用CPU和IO同时执行的原理,实现函数异步执行

实现协程就是要多个任务的循环,await是挂起命令。每到一个地方await一次,然后await返回。知道最终全部返回,程序结束。

关键对象

  1. 事件循环 - Eventloop :协程不能直接运行,需要把协程加到时间循环(loop).
  2. 协程对象,协程方法. async/await 原生协程 协程方法实现
  3. Future & Task 对象。 task – loop机制 (多个任务循环)
  4. 注册事件循环. 将协程注册到事件循环并启动。loop.run_until_completed会阻塞程序直到所有的协程对象都执行完毕。
  5. 启动结果获取。 task.result()

1. 事件循环

协程代码之间不断切换,形成的闭环范围作为一个事件循环.

#获取事件循环
loop = asyncio.get_event_loop()

2. 协程对象

协程对象,指一个使用async关键字定义的函数。它的调用不会立即执行函数,需要注册到事件循环,由事件循环调用。

本质:被 async 关键定义的协程,实际上是一个Coroutine对象,而Coroutine又继承Awaitable
from collections.abc import Coroutine, Awaitable

# 定义协程函数
async def work(self):
    while self.que.qsize() > 0:
        try:
            url = self.que.get(timeout=1)
            await asyncio.sleep(1)
            print(f"{os.getpid()}正在请求url:{url}")
            return os.getpid()
        except Exception as e:
            import  traceback
            print("---------------err", traceback.format_exc())
            break

3. Future & Task 对象

具体执行的任务

创建Task的方法
# 方法1:使用ensure_future方法。future代表一个对象,未执行的任务。
task1 = asyncio.ensure_future(func1(1))
task2 = asyncio.ensure_future(func1(2))

# 方法3:使用Python 3.7提供的asyncio.create_task方法
task1 = asyncio.create_task(func1(1))
task2 = asyncio.create_task(func1(2))

创建Future对象的时候,状态为pending,事件循环调用执行的时候就是running,调用完毕就是done,如果需要取消Future对象的调度执行,可调用Future对象的cancel()函数。

Future对象方法
result():立即返回Future对象运行结果或者抛出执行时的异常,没有timeout参数,如果Future没有完成,不会阻塞等待结果,而是直接抛出InvalidStateError异常。最好的方式是通过await获取运行结果,await会自动等待Future完成返回结果,也不会阻塞事件循环,因为在asyncio中,await被用来将控制权返回给事件循环。

done():非阻塞的返回Future对象是否成功取消或者运行结束或被设置异常,而不是查看future是否已经执行完成。
cancelled():判断Future对象是否被取消。
add_done_callback():传入一个可回调对象,当Future对象done时被调用。
exception():获取Future对象中的异常信息,只有当Future对象done时才会返回。
get_loop():获取当前Future对象绑定的事件循环。

4. 注册事件循环

创建demo1
#获取时间循环
loop = asyncio.get_event_loop()
#创建任务任务asyncio.ensure_future
task = [asyncio.ensure_future(work1(i)) for i in range(5)]
# 将任务注册到时间循环里
loop.run_until_complete(asyncio.wait(task))
loop.close()
创建demo2

这里使用asyncio.gather收集任务列表,无需创建函数

import asyncio

async def func1(i):
    print(f"协程函数{i} 马上执行")
    await asyncio.sleep(2)  
    print(f"协程函数{i} 执行完毕!")

async def main():
    tasks = []
    for i in  range(1, 5):
        #这里未由协程函数创建协程任务
        tasks.append(func1(i))

    # gather自动将函数列表封装成了协程任务
    await  asyncio.gather(*tasks)

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

注意点: 协程的实现函数必须要通过await asyncio.sleep(time)来实现。 time.sleep是没效果的

5. 启动方式

  • loop.run_until_complete(asyncio.gather(*tasks)) #gather自动将函数列表封装成了协程任务,返回结果顺序
  • loop.run_until_complete(asyncio.wait(task))

6. 返回结果处理

asyncio.wait 会返回两个值:done 和 pending,done 为已完成的协程任务列表,pending 为超时未完成的协程任务类别,需通过task.result()方法可以获取每个协程任务返回的结果;

asyncio.gather 返回的是所有已完成协程任务的 result,不需要再进行调用或其他操作,就可以得到全部结果。

demo1
import asyncio
async def func1(i):
    print(f"协程函数{i}马上执行。")
    await asyncio.sleep(2)
    return i
async def main():
    tasks = []
    for i in range(1, 5):
        tasks.append(asyncio.create_task(func1(i)))
    # 获取任务结果
    done, pending = await asyncio.wait(tasks)
    for task in done:
        print(f"执行结果: {task.result()}")

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

demo2: 使用loop_run 执行,task.result 返回参数

def run(self):
    task = []
    # 开启多协程
    loop = asyncio.get_event_loop()
    for i in range(1000):
    # 调用工作函数
    task.append(asyncio.ensure_future(self.work()))
    loop.run_until_complete(asyncio.wait(task))
    #loop.run_until_complete(asynciogather(*tasks))  #这种有序返回也可以
    
    res = [t.result() for t in task ]
    loop.close()
    print(res)
demo3: asynico.gather 按执行顺序返回结果
# gather自动将函数列表封装成了协程任务
results = await asyncio.gather(*tasks)
for res in results:
print("-------------res",res)
  • gather具有把普通协程函数包装成协程任务的能力,wait没有。wait只能接收包装后的协程任务列表做参数。
  • 两者返回值不一样,wait返回的是已完成和未完成任务的列表,而gather直接返回协程任务执行结果。
  • gather返回的任务执行结果是有序的,wait方法获取的结果是无序的。

多线程 + 多协程

import asyncio
import os
import time
from multiprocessing import Process, Queue


def time_count(func):
    def wrapper(*args, **kwargs):
        start_time = time.time()
        func(*args, **kwargs)
        end_time = time.time()
        print("总耗时:", end_time - start_time)
    return wrapper

class Myprocess(Process):
    def __init__(self, que):
        super().__init__()
        self.que = que

    # 重写run方法
    def run(self):
        task = []
        # 开启时间循环
        loop = asyncio.get_event_loop()
        for i in range(1000):
            # 调用工作函数
            task.append(asyncio.ensure_future(self.work()))
        loop.run_until_complete(asyncio.wait(task))
        # loop.run_until_complete(asyncio.gather(*task))  两种方法都可以
        res = [t.result() for t in task]
        loop.close()
        print(res)

    # 定义工作函数
    async def work(self):
        while self.que.qsize() > 0:
            try:
                url = self.que.get(timeout=1)
                await asyncio.sleep(1)
                print(f"{os.getpid()}正在请求url:{url}")
                return os.getpid()
            except Exception as e:
                break
                
@time_count
def main():
    q = Queue()
    for i in range(10000):
        q.put(f"https://www.baidu.com--{i}")
    process_list = []
    for i in range(4):
        p = Myprocess(q)
        process_list.append(p)
        p.start()
    for p in process_list:
        p.join()
    print("任务结束")

if __name__ == '__main__':
    main()

# 使用asyncio改写, 加入函数返回值
# 总耗时: 5.137028455734253

参考

  • https://www.jianshu.com/p/75b445a55536
  • https://blog.csdn.net/qq_37674086/article/details/113879117
  • https://zhuanlan.zhihu.com/p/169426477 #通俗易懂

你可能感兴趣的:(python,python,开发语言)