zoukankan      html  css  js  c++  java
  • 深入理解协程(三):async/await实现异步协程

    原创不易,转载请联系作者

    深入理解协程分为三部分进行讲解:

    • 协程的引入
    • yield from实现异步协程
    • async/await实现异步协程

    本篇为深入理解协程系列文章的最后一篇

    从本篇你将了解到:

    1. async/await的使用。
    2. 如何从yield from风格的协程修改为async/await风格。

    篇幅较长,请耐心阅读。

    async/await的引入

    上篇【yield from实现异步协程】我们引入了asynico模块,结合yield from实现异步协程。但语法不够简洁,其中涉及的生成器装饰器也让人头疼不已。

    为了语法更加简洁。于是,在Python3.5(PEP 492)中新增了async/await语法来实现异步协程。

    async/await的使用

    先介绍几个概念:

    • async/await :python3.5之后用于定义协程的关键字,async定义一个协程,await用于挂起阻塞的异步调用接口。
    • event_loop :事件循环,程序开启一个无限的循环,程序员会把一些函数注册到事件循环上。当满足事件发生的时候,调用相应的协程函数。
    • coroutine :协程对象,指一个使用async关键字定义的函数,它的调用不会立即执行函数,而是会返回一个协程对象。协程对象需要注册到事件循环,由事件循环调用。
    • task :任务,是对协程进一步封装,其中包含任务的各种状态。
    • future: 代表将来执行或没有执行的任务的结果。它和task上没有本质的区别

    1.创建协程

    在def前加上async的声明,就完成了一个协程函数的定义。协程函数不能直接调用运行,需要将协程注册到事件循环,并启动事件循环才能使用

    import asyncio
    
    async def fun(a):    # 定义协程函数
        print(a)
    
    # 调用协程函数,生成一个协程对象,此时协程函数并未执行
    coroutine = fun('hello world')
    # 创建事件循环
    loop = asyncio.get_event_loop()
    # 将协程函数添加到事件循环,并启动
    loop.run_until_complete(coroutine)
    
    # 输出
    hello word
    

    2. 任务对象task

    协程对象不能直接运行,在注册事件循环的时候,其实是run_until_complete方法将协程包装成为了一个任务(task)对象。我们也可以显式实现它。

    实现方式1:

    使用create_task()创建task。

    import asyncio
    
    async def fun(a):
        print(a)
        return a
    
    coroutine = fun('hello world')
    loop = asyncio.get_event_loop()
    # 使用create_task()创建task,并将coroutine对象转化成task对象
    task = loop.create_task(coroutine)
    print(f'task: {task}')
    loop.run_until_complete(task)
    print(f'task: {task}')
    print(f'result: {result}')
    

    输出结果:

    task: <Task pending coro=<fun() running at D:/test.py:3>>
    hello world
    task: <Task finished coro=<fun() done, defined at D:/test.py:3> result='hello world'>
    

    从输出结果能看到,创建task对象后,未将task添加到事件循环之前,状态是pending;task对象执行完毕后,状态是finished,并将参数a的值返回。

    实现方式2:

    使用asyncio 的 ensure_future() 方法,创建task。

    import asyncio
    
    async def fun(a):
        print(a)
        return a
    
    coroutine = fun('hello world')
    # 使用asyncio 的 ensure_future() 方法,创建task,并将coroutine对象转化成task对象
    task = asyncio.ensure_future(coroutine)
    loop = asyncio.get_event_loop()
    print(f'task: {task}')
    loop.run_until_complete(task)
    print(f'task: {task}')
    

    输出结果:

    task: <Task pending coro=<fun() running at D:/test.py:3>>
    hello world
    task: <Task finished coro=<fun() done, defined at D:/test.py:3> result='hello world'>
    

    通过ensure_future() 可以在loop未定义前创建task。实现效果与上面相同。

    3.绑定回调函数

    如果需要在task执行完毕后对结果进行处理,可以通过给task绑定回调函数完成,回调的最后一个参数是future对象(如task对象)。

    import asyncio
    
    async def fun(a):
        print(a)
        return a
    
    def callback(task):	# 回调函数,打印task的返回值
        print(f'result: {task.result()}')
    
    coroutine = fun('hello world')
    loop = asyncio.get_event_loop()
    task = loop.create_task(coroutine)
    task.add_done_callback(callback)	#绑定回调函数
    print(f'task: {task}')
    loop.run_until_complete(task)
    print(f'task: {task}')
    

    输出结果:

    task: <Task pending coro=<fun() running at D:/test.py:3> cb=[callback() at D:/Study/Python/python_text/非项目/协程.py:7]>
    hello world
    result: hello world	# 完成了返回值的打印
    task: <Task finished coro=<fun() done, defined at D:/test.py:3> result='hello world'>
    

    4.多任务协程

    如果我们需要执行多个任务时,我们可以定义一个任务列表,并将需要完成的协程任务都加进去。将原本的loop.run_until_complete(tasks)改为loop.run_until_complete(asyncio.wait(tasks))

    如果执行的是多个耗时的任务,如网络请求、文件读取等。此时就await就派上用场了,await可以针对耗时的操作进行挂起,就像生成器里的yield一样,函数让出控制权。协程遇到await,事件循环将会挂起该协程,执行别的协程,直到其他的协程也挂起或者执行完毕,再进行下一个协程的执行

    举个例子:

    import time
    import asyncio
    
    async def taskIO_1():	
        print('开始运行IO任务1...')
        await asyncio.sleep(2)  
        print('IO任务1已完成,耗时2s')
        return taskIO_1.__name__
    
    async def taskIO_2():		
        print('开始运行IO任务2...')
        await asyncio.sleep(3)  
        print('IO任务2已完成,耗时3s')
        return taskIO_2.__name__
    
    if __name__ == '__main__':
        start = time.time()
        loop = asyncio.get_event_loop() 
        tasks = [taskIO_1(), taskIO_2()]
        loop.run_until_complete(asyncio.wait(tasks)) # 完成事件循环,直到最后一个任务结束
        print('所有IO任务总耗时%.5f秒' % float(time.time()-start))
        
    # 输出
    开始运行IO任务2...
    开始运行IO任务1...
    IO任务1已完成,耗时2s
    IO任务2已完成,耗时3s
    所有IO任务总耗时3.00251秒
    

    可以看出,原本需要5秒,现在执行只需要3秒。

    yield from转async/await

    上述代码有没有很眼熟。

    其实,这段代码正是【yield from实现异步协程】末尾,yield from结合asynico实现异步协程的代码。只是将yielf from风格变为async/await风格。

    由于async/awaityield from风格的协程底层实现方式相同。因此,从yield from风格改为async/await风格非常容易。只需:

    • @asyncio.coroutine替换为async
    • yield from替换为await

    async/await风格的代码隐藏了装饰器、yield from语法,方便了人们的理解,同时也让代码更加简洁。

    推荐阅读

    深入理解协程(一):协程的引入

    深入理解协程(二):yield from实现异步协程

    年底送福利,关注公众号【西加加先生】,2020一起玩Python,公众号回复【2020】即可获取抽奖方式参与抽奖
    在这里插入图片描述

  • 相关阅读:
    解决 未能为数据库 '数据库用户名' 中的对象 '表名' 分配空间,因为文件组 'PRIMARY' 已满
    获取一个目录下文件扩展名为txt或htm或html的文件的几种方法
    由于 Web 服务器上的“ISAPI 和 CGI 限制”列表设置,无法提供您请求的页面
    图解C#创建SqlServer MD5 加密函数
    SqlServer 日期转换 所有格式
    使用SoapHeader对WebService进行身份验证
    禁用文本框粘贴功能
    去除 以下文件中的行尾不一致,要将行尾标准化吗 的提示
    程序锁定windows系统以及调用其它系统对话框,如控制面板,重启系统
    yakuake shell
  • 原文地址:https://www.cnblogs.com/ghostlee/p/12190056.html
Copyright © 2011-2022 走看看