快捷搜索:  汽车  科技

python异步协程在哪里(python协程asyncio的个人理解)

python异步协程在哪里(python协程asyncio的个人理解)asyncio.run(coro *** debug=False)await后面需要跟一个可等待对象(awaitable),有下面三种可等待对象:import asyncio async def aaa(): print('hello') asyncio.run(aaa()) # 输出------------------------- hello 使用await进行异步等待在协程函数中最重要的功能是使用await语法等待另一个协程,这将挂起当前协程,直到另一个协程返回结果。await的作用:挂起 coroutine 的执行以等待一个 awaitable 对象。 只能在 coroutine function 内部使用。import asyncio async def aaa(): print('hello') async def m

协程与任务

python语境中,协程 coroutine 的概念有两个:协程函数、协程对象,协程对象由协程函数创建得到(类似于类实例化得到一个对象).

理解协程,最重要的是了解事件循环和任务执行的机制,下面是三个原则:

  • 事件循环中,不断循环执行各个任务,若一个任务遇到await或者执行完成,则返回控制权给事件循环,这时候事件循环再去执行下一个任务
  • 事件循环同一时刻只会运行一个任务
  • 协程不会被加入事件循环的执行日程,只有被注册为任务之后,事件循环才可以通过任务来设置日程以便并发执行协程
基本语法协程的声明和运行

使用async def语句定义一个协程函数,但这个函数不可直接运行

async def aaa(): print('hello') print(aaa()) # 输出---------------------------------- <coroutine object aaa at 0x7f4f9a9dfec0> /root/Project/test01/test2.py:4: RuntimeWarning: coroutine 'aaa' was never awaited print(aaa()) RuntimeWarning: Enable tracemalloc to get the object allocation traceback

如何运行一个协程呢,有三种方式:

  1. 使用asyncio.run()函数,可直接运行

import asyncio async def aaa(): print('hello') asyncio.run(aaa()) # 输出------------------------- hello

  1. 使用await进行异步等待

在协程函数中最重要的功能是使用await语法等待另一个协程,这将挂起当前协程,直到另一个协程返回结果。

await的作用:挂起 coroutine 的执行以等待一个 awaitable 对象。 只能在 coroutine function 内部使用。

import asyncio async def aaa(): print('hello') async def main(): await aaa() asyncio.run(main())

  1. 使用asyncio.create_task() 函数来创建一个任务,放入事件循环中

import asyncio async def aaa(): print('hello') async def main(): asyncio.create_task(aaa()) asyncio.run(main())可等待对象

上面说过,协程函数中最重要的功能是使用await语法等待另一个协程,这将挂起当前协程,直到另一个协程返回结果。(重要,重复一遍)

await后面需要跟一个可等待对象(awaitable),有下面三种可等待对象:

  • 协程:包括协程函数和协程对象
  • 任务:通过asyncio.create_task()函数将协程打包为一个任务
  • Futures:特殊的 低层级 可等待对象,表示一个异步操作的 最终结果
运行asyncio程序

asyncio.run(coro *** debug=False)

传入协程coroutine coro ,创建事件循环,运行协程返回结果,并在结束时关闭,应当被用作 asyncio 程序的主入口点。

创建任务

asyncio.create_task(coro *** name=None)

将 coro 协程 打包为一个 Task 排入日程准备执行。返回 Task 对象。

休眠

coroutine asyncio.sleep(delay result=None *** loop=None)

阻塞 delay 指定的秒数,该协程总是会挂起当前任务,以允许其他任务运行

机制解析

通过官网的两段代码,来详细解析一下协程的运行机制。

官方两个代码如下,注意看输出差异:

代码1,通过协程对象来执行

import asyncio import time async def say_after(delay what): await asyncio.sleep(delay) print(what) async def main(): print(f"started at {time.strftime('%X')}") await say_after(1 'hello') await say_after(2 'world') print(f"finished at {time.strftime('%X')}") asyncio.run(main()) # 1: 创建事件循环,传入入口点main()协程对象 此时生成一个对应的task

输出为:

started at 17:13:52 hello world finished at 17:13:55

代码2,通过任务来执行

import asyncio import time async def say_after(delay what): await asyncio.sleep(delay) print(what) async def main(): task1 = asyncio.create_task( say_after(1 'hello')) task2 = asyncio.create_task( say_after(2 'world')) print(f"started at {time.strftime('%X')}") await task1 await task2 print(f"finished at {time.strftime('%X')}") asyncio.run(main())

输出:

started at 17:14:32 hello world finished at 17:14:34

注意到运行时间比前一个代码快1秒,下面说明为什么出现这种情况(文字比较多)。

代码一的运行逻辑:

asyncio.run(main()) 启动一个事件循环,将入口点main()协程对象传入,生成一个对应的任务task_main;

事件循环运行任务task_main,然后执行第1条代码:print(f"started at {time.strftime('%X')}");

接着执行第2条代码:await say_after(1 'hello'),第2条代码首先生成一个say_after(1 'hello')协程对象,同时生成该协程对象对应的task_1;

由于await语法,task_main任务将控制权返回给事件循环,同时告诉事件循环需要等待task1才能继续运行;

事件循环获得控制权后,发现此时有两个任务task_main和task1,同时task_main在等待task1,于是会去执行task1任务;

task1任务将执行第1条代码:await asyncio.sleep(1),同样会生成asyncio.sleep(1)协程对象,以及对应的任务task2,同时因await语法将控制权返回给事件循环;

事件循环获得控制权后,发现此时有三个任务task_main、task1、task2,由于task_main、task1都处于等待状态,于是执行task3;

task3在1秒后运行完成,返回控制权给事件循环;

事件循环获得控制权,发现此时有两个任务task_main和task1,同时task_main在等待task1,于是会去执行task1任务;

task1任务执行第2条代码:print('hello'),执行完成后,任务也运行结束,将控制权返回给事件循环;

事件循环获得控制权后,发现此时有一个任务task_main,于是接着执行下一条代码:await say_after(2 'world'),继续重复上述过程,直到这个协程任务结束;

task_main执行最后一条代码;

事件循环关闭退出;

代码二的运行逻辑:

asyncio.run(main()) 启动一个事件循环,将入口点main()协程对象传入,生成一个对应的任务task_main;

事件循环运行任务task_main,然后执行前几条代码,创建两个任务task1、task2,并注册到事件循环中(此时事件循环一共有3个task),随之执行程序直到await;

第一个await:await task1,这里会阻塞当前任务task_main并将控制权返回给事件循环,事件循环获取控制权,安排执行下一个任务task1;

task1任务开始执行,直至遇到await asyncio.sleep(1),asyncio.sleep(1)协程对象开始异步执行,同时task1返回控制权给事件循环,事件循环获取控制权后安排执行下一个任务task2;

task2任务开始执行,直至遇到await asyncio.sleep(2),asyncio.sleep(2)协程对象开始异步执行,同时task2返回控制权给事件循环,事件循环获取控制权后安排执行下一个任务;

此时3个任务均处于await状态,事件循环保持等待;

1秒后asyncio.sleep(1)执行完成,task1取消阻塞,事件循环将安排task1执行,task1执行完成后返回控制权给事件循环,此时事件循环中一共两个任务task_main、task2。

此时task2任务处于await状态,而task_main也取消了阻塞,事件循环安排task_main执行,执行一行代码后遇到await task2,于是返回控制权给事件循环;

此时2个任务均处于await状态,事件循环保持等待;

1秒后asyncio.sleep(2)执行完成,task2取消阻塞,事件循环将安排task2执行,task2执行完成后返回控制权给事件循环,此时事件循环中只剩任务task_main;

于是事件循环安排task_main执行,task_main执行完成,asyncio.()函数收到信息也结束运行,整个程序结束

运行的流程图示

(任务就绪后,就等待事件循环来调用了,此时需要await来阻塞主任务task_main,否则控制权一直在task_main手上,导致task_main任务执行完成,run()收到main()执行结束的消息后,事件循环也关闭并结束,程序也将退出)

python异步协程在哪里(python协程asyncio的个人理解)(1)

其实将第2个代码中的await task1删除,只保留await task2,结果中的输出相同,并消耗相同的总时间。但只保留await task1的话,将没有task2的输出;
如果将第2个代码中的await task1和await task2都删除,换成await asyncio.sleep(3),一样会打印相同输出,不过总时间会变为3秒;

其中的原因需要理解协程的工作机制(事件循环和控制权)

python异步协程在哪里(python协程asyncio的个人理解)(2)


文章来自https://www.cnblogs.com/MrReboot/p/16413332.html

猜您喜欢: