2 异步编程 asyncio模块

异步编程 asyncio模块

课程链接(《asyncio异步编程》,讲师:武沛齐老师)

1 事件循环

事件循环本质是循环,负责不断地监测并执行特定的代码。

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

while True:
	1. 两个列表:就绪任务列表 和 已完成的任务列表
	2. 遍历检查任务列表中的每一个任务的状态,
	任务状态分为可执行,不可执行,已完成
	将可执行的任务放入可执行的任务列表,
	将已完成的任务放入已完成的任务列表,
	如果任务是不可执行的,说明这个任务此时可能遇到IO等阻塞操作,处于等待状态,本次循环不处理不可执行的任务。
	
    for 可执行的任务 in  可执行的任务列表:
        执行可执行的任务

	for 已完成的任务 in 已完成的任务列表:
		从任务列表中移除已完成的任务

	if 任务列表为空(说明所有任务均已完成):
	    终止循环
import asyncio

# 获取一个事件循环
event_loop = asyncio.get_event_loop()
# 将任务放入任务列表中,并通过事件循环监测并执行任务
event_loop.run_until_complete(asyncio.wait(task_list))

2 快速上手

协程函数:使用async def 语句定义的函数。
协程对象:执行协程函数得到的是协程对象。

# 协程函数
async def func():
    pass

# 协程对象
coroutine_obj = func()
# sys:1: RuntimeWarning: coroutine 'func' was never awaited

执行协程函数时,协程函数内部代码不会执行,只是获取一个协程对象,必须与事件循环配合使用,即将协程对象交给事件循环处理。

import asyncio

async def func():
    print(123)

coroutine_obj = func()

event_loop = asyncio.get_event_loop()
event_loop.run_until_complete(coroutine_obj)

python3.7 简便写法

import asyncio

async def func():
    print(123)

coroutine_obj = func()

asyncio.run(coroutine_obj)

3 await关键字

await = async + wait
await + 可等待的对象
可等待的对象包括 协程对象、Task对象 和 Future对象

await就是等待后面的对象有了结果后,才继续执行。
在等待期间,线程会切换并执行其它任务,当等待结束时,这个任务的状态变为可执行,事件循环会在下一次循环时检测任务状态并继续执行这个任务。

import asyncio

async def func():
    print(123)
    res = await asyncio.sleep(1)
    print(res)  # None

asyncio.run(func())
import asyncio

async def func1():
    print('func1 start.')
    await asyncio.sleep(1)
    print('func1 end.')
    return 'func1'

async def func2():
    print('func2 start.')
    res1 = await func1()
    print(res1)
    res2 = await func1()
    print(res2)
    print('func2 end.')

asyncio.run(func2())
'''
func2 start.
func1 start.
func1 end.
func1
func1 start.
func1 end.
func1
func2 end.
'''

4 Task对象

Tasks are used to schedule coroutines concurrently.
When a coroutine is wrapped into a Task with functions like asyncio.create_task() the coroutine is automatically scheduled to run soon.

Tasks用于并发地调度当前的协程,可以简单地理解为Task对象用于立即将任务放入事件循环中,或者是在事件循环中并发地创建多个任务。
通过使用 asyncio.create_task(协程对象) 函数创建Task对象,并将任务立即添加到事件循环中,等待被调度执行。
注意,这里的并发实质上是串行,通过在一个线程中快速切换任务达到了并发的效果。

创建Task对象除了使用asyncio.create_task(协程对象) 函数(Python 3.7),还可以使用底层级的函数 loop.create_task() 和 ensure_future()。
不建议手动进行Task对象的实例化。

import asyncio

async def func():
    print('func start.')
    await asyncio.sleep(1)
    print('func end.')
    return 'func'

async def main():
    print('main start.')
	
	# 创建Task对象,并将任务func添加到事件循环中。
    task1 = asyncio.create_task(func())
    task2 = asyncio.create_task(func())
	# 目前事件循环中存在三个任务,主任务main,task1和task2
	
    print('main end.')

    res1 = await task1
    res2 = await task2

    print(res1, res2)


asyncio.run(main())  # 将任务main添加到事件循环中
'''
main start.
main end.
func start.
func start.
func end.
func end.
func func
'''

先执行主任务main,当运行到 res1 = await task1 时,等待任务task1,执行子任务task1,当运行到func中的 await asyncio.sleep(1) 时,切换任务列表中的另一个任务,此时主任务main必须等待子任务task1返回结果才能继续执行,因此执行的是任务列表中的另一个子任务task2,当运行到func中的 await asyncio.sleep(1) 时,继续切换任务,直到子任务task1和task2执行结束获得返回结果,主任务继续执行。

import asyncio

async def func():
    print('func start.')
    await asyncio.sleep(1)
    print('func end.')
    return 'func'

async def main():
    print('main start.')

    task1 = asyncio.create_task(func())
    task2 = asyncio.create_task(func(), name='自定义名字Task')

    task_list = [task1, task2, ]

    print('main end.')
    done, pending = await asyncio.wait(task_list)

    # 可以为所有任务设置超时时间
    # done, pending = await asyncio.wait(task_list, timeout=1)

    # done是个集合,包含了task_list中所有的Task对象以及结果。
    print(done)
    # {<Task finished name='Task-2' coro=<func() done, defined at D:/demo.py:25> result='func'>, <Task finished name='自定义名字Task' coro=<func() done, defined at D:/demo.py:25> result='func'>}

    # 如果设置了timeout,执行超时的话,done集合为空,pending集合则包含了未完成的Task对象。
    print(pending)  # set()


# event_loop = asyncio.get_event_loop()
# event_loop.run_until_complete(coroutine_obj)
asyncio.run(main())

报错代码

import asyncio

async def func():
    print('func start.')
    await asyncio.sleep(1)
    print('func end.')
    return 'func'


task_list = [
    asyncio.create_task(func(), name='T1'),  # Task对象
    # sys:1: RuntimeWarning: coroutine 'func' was never awaited
    asyncio.create_task(func(), name='T2'),
]


done, padding = asyncio.run(asyncio.wait(task_list))

函数asyncio.create_task会创建Task对象并将其立即添加到事件循环中,创建事件循环是在函数asyncio.run中完成的,执行函数asyncio.create_task时不存在事件循环,因此报错。

import asyncio

async def func():
    print('func start.')
    await asyncio.sleep(1)
    print('func end.')
    return 'func'


task_list = [
    func(),  # 协程对象
    func(),
]


# asyncio.run会在生成事件循环后自动根据task_list中的协程对象创建Task对象。
done, padding = asyncio.run(asyncio.wait(task_list))

5 asyncio.Future对象

模块asyncio的Future类是Task类的基类。

A Future is a special low-level awaitable object that represents an eventual result of an asynchronous operation.

模块asyncio中的Future相当于底层的接口,用于等待异步处理的结果。
Task类的方法await是基于Future类中的一个属性_state,它用于存储维护任务的状态。

import asyncio

async def main():
    loop = asyncio.get_running_loop()  # 获取当前的事件循环
    fut = loop.create_future()  # 创建Future对象,但未绑定任何行为,因此该任务不会有结果。
    await fut  # 等待Future对象的任务结果,但始终没有结果,因此会一直等待。

asyncio.run(main())
import asyncio


async def set_after(fut):
    await asyncio.sleep(1)
    fut.set_result('123')


async def main():
    loop = asyncio.get_running_loop()
    fut = loop.create_future()

    # 创建Task对象,绑定了函数set_after,其参数是Future对象。
    # 函数set_after会为Future对象手动设置结果,因此Future对象的任务有了执行结果。
    await loop.create_task(set_after(fut))

    # 此时Future对象的任务有执行结果,可以结束等待。
    res = await fut
    print(res)  # 123

asyncio.run(main())

6 concurrent.futures.Future对象

模块asyncio的Future对象是在使用进程池/线程池实现异步效果的时候使用的。

import time
from concurrent.futures import Future
from concurrent.futures.thread import ThreadPoolExecutor
from concurrent.futures.process import ProcessPoolExecutor


def func(val):
    time.sleep(1)
    print(val)

# 创建线程池
pool = ThreadPoolExecutor(max_workers=5)
# 创建进程池
# pool = ProcessPoolExecutor(max_workers=5)

for i in range(10):
    fut = pool.submit(func, i)
    print(fut)

协程与进程池和线程池交叉使用
使用场景:使用某个第三方模块,而这个模块不支持协程,此时需要包装。

  1. 使用默认的线程池
import time
import asyncio
import concurrent.futures


def func1():
    time.sleep(1)
    return 'func1'


async def main():
    loop = asyncio.get_running_loop()

    # 1. 创建一个线程池;
    # 2. 调用ThreadPoolExector的方法submit,在线程池中申请一个线程用于执行函数func1,
    # 返回一个concurrent.futures.Future对象;
    # 3. 调用函数asyncio.wrap_future,将concurrent.futures.Future对象包装为asyncio.Future对象;
    # 因为concurrent.futures.Future对象不支持await语法,所以需要将其包装为asyncio.Future对象。
    fut = loop.run_in_executor(None, func1)  # 参数None表示使用默认的线程池。
    res = await fut
    print(f'Default thread pool {res}')  # Default thread pool func1

asyncio.run(main())
  1. 使用自定义的线程池
import time
import asyncio
import concurrent.futures


def func1():
    time.sleep(1)
    return 'func1'


async def main():
    loop = asyncio.get_running_loop()

    with concurrent.futures.ThreadPoolExecutor() as pool:
        res = await loop.run_in_executor(pool, func1)
        print(f'Custom thread {res}')  # Custom thread func1


asyncio.run(main())
  1. 使用自定义的进程池
import time
import asyncio
import concurrent.futures


def func1():
    time.sleep(1)
    return 'func1'


async def main():
    loop = asyncio.get_running_loop()

    with concurrent.futures.ProcessPoolExecutor() as pool:
        res = await loop.run_in_executor(pool, func1)
        print(f'Custom process {res}')  # Custom process func1

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

7 案例 asyncio + 不支持异步的模块

import asyncio
import requests


async def download_image(url):
    print('开始下载。')
    loop = asyncio.get_event_loop()

    # requests模块默认不支持异步操作,可以配合线程池实现异步操作
    # 会创建多个线程
    fut = loop.run_in_executor(None, requests.get, url)

    resp = await fut
    print('下载完成。')

    file_name = url.rsplit('/')[-1]
    with open(file_name, mode='wb') as file_obj:
        file_obj.write(resp.content)


if __name__ == '__main__':
    url_list = [
        'http://up.deskcity.org/pic_source/d7/90/03/d790030b9ba42ba6e65f120cbdc3fd92.jpg',
        'http://vg01.oss-cn-hangzhou.aliyuncs.com/photo/web/160907133030907.jpg',
    ]
    task_list = [download_image(each_url) for each_url in url_list]

    loop = asyncio.get_event_loop()
    loop.run_until_complete(asyncio.wait(task_list))

8 异步迭代器

迭代器:实现了方法__iter__()和__next__()的对象。
可迭代对象:实现了内置有__iter__()方法的对象。

异步迭代器:实现了方法__aiter__()和__anext__()的对象,aiter__必须返回一个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 is None:
            raise StopAsyncIteration
        return val


async def func():
    obj = Reader()
    # async for需要处于协程函数内
    async for item in obj:
        print(item)

9 异步上下文管理器

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

import asyncio


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

    async def do_sth(self):
        # 异步操作数据库
        return 123

    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():
    obj = AsyncContextManager()
    # async with需要处于协程函数内
    # 进入异步上下文管理器时先执行方法__aenter__,
    # f是方法__aenter__的返回结果。
    # 离开异步上下文管理器时执行方法__aexit__。
    async with obj as f:
        res = await f.do_sth()
        print(res)


asyncio.run(func())

10 事件循环升级 uvloop

uvloop是asyncio中的事件循环的替代方案,大幅度提高事件循环效率和性能。

import asyncio
import uvloop
# 将asyncio中的事件循环替换为uvloop
asyncio.set_event_loop_policy(uvloop.EventLoopPolicy())


# 事件循环会自动使用uvloop
asyncio.run()
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值