低阶API介绍

asyncio中低阶API的种类很多,涉及到开发的5个方面。包括:

  1. 获取事件循环
  2. 事件循环方法集
  3. 传输
  4. 协议
  5. 事件循环策略

本篇中只讲解asyncio常见常用的函数,很多底层函数如网络、IPC、套接字、信号等不在本篇范围。

获取事件循环

事件循环是异步中重要的概念之一,用于驱动任务的执行。包含的低阶API如下:

函数 功能
asyncio.get_running_loop() 获取当前运行的事件循环首选函数。
asyncio.get_event_loop() 获得一个事件循环实例
asyncio.set_event_loop() 将策略设置到事件循环
asyncio.new_event_loop() 创建一个新的事件循环

在asyncio初识这篇中提到过事件循环,可以把事件循环当做是一个while循环,在周期性的运行并执行一些任务。这个说法比较抽象,事件循环本质上其实是能调用操作系统IO模型的模块。以Linux系统为例,IO模型有阻塞,非阻塞,IO多路复用等。asyncio 常用的是IO多路复用模型的epoolkqueue。事件循环原理涉及到异步编程的操作系统原理,后续更新一系列相关文章。

get_event_loop()

创建一个事件循环,用于驱动协程的执行

import asyncio

async def demo(i):
print(f"hello {i}") def main():
loop = asyncio.get_event_loop()
print(loop._selector)
task = loop.create_task(demo(1))
loop.run_until_complete(task) main()

结果:

<selectors.KqueueSelector object at 0x104eabe20>
hello 1

可以通过loop._selector属性获取到当前事件循环使用的是kqueue模型

获取循环

import asyncio

async def demo(i):
res = asyncio.get_running_loop()
print(res)
print(f"hello {i}") def main():
loop = asyncio.get_event_loop()
task = loop.create_task(demo(1))
loop.run_until_complete(task)
main()

结果:

<_UnixSelectorEventLoop running=True closed=False debug=False>
hello 1

推荐使用asyncio.run 创建事件循环,底层API主要用于库的编写。

生命周期

生命周期是用于管理任务的启停的函数,如下:

函数 功能
loop.run_until_complete() 运行一个期程/任务/可等待对象直到完成。
loop.run_forever() 一直运行事件循环,直到被显示停止
loop.stop() 停止事件循环
loop.close() 关闭事件循环
loop.is_running() 返回 True , 如果事件循环正在运行
loop.is_closed() 返回 True ,如果事件循环已经被关闭
await loop.shutdown_asyncgens() 关闭异步生成器

run_until_complete

运行一个期程/任务/可等待对象直到完成。run_until_complete的参数是一个futrue对象。当传入一个协程,其内部会自动封装成task。run_until_complete()是会自动关闭事件循环的函数,区别于run_forever()是需要手动关闭事件循环的函数。

import asyncio 

async def demo(i):
print(f"hello {i}") def main():
loop = asyncio.get_event_loop() task = loop.create_task(demo(1)) # 传入的是一个任务
loop.run_until_complete(task) # 传入的是一个协程也可以
loop.run_until_complete(demo(20)) main()

结果:

hello 1
hello 20

调试

函数 功能
loop.set_debug() 开启或禁用调试模式
loop.get_debug() 获取当前测试模式

调度回调函数

在异步编程中回调函数是一种很常见的方法,想要在事件循环中增加一些回调函数,可以有如下方法:

函数 功能
loop.call_soon() 尽快调用回调。
loop.call_soon_threadsafe() loop.call_soon() 方法线程安全的变体。
loop.call_later() 在给定时间之后调用回调函数。
loop.call_at() 在指定的时间调用回调函数。

这些回调函数既可以回调普通函数也可以回调协程函数。

call_soon

函数原型:

loop.call_soon(callback, *args, context=None)

示例:

import asyncio

async def my_coroutine():
print("协程被执行") async def other_coro():
print("非call_soon调用") def callback_function():
print("回调函数被执行") # 创建一个事件循环
loop = asyncio.get_event_loop() # 使用create_task包装协程函数,并调度执行
loop.call_soon(loop.create_task, my_coroutine()) # 调度一个常规函数以尽快执行
loop.call_soon(callback_function) # 启动一个事件循环
task = loop.create_task(other_coro())
loop.run_until_complete(task)

结果:

回调函数被执行
非call_soon调用
协程被执行

结果分析:

call_soon调用普通函数直接传入函数名作为参数,调用协程函数需要讲协程通过loop.create_task封装成task。

线程/进程池

函数 功能
await loop.run_in_executor() 多线程中运行一个阻塞的函数
loop.set_default_executor() 设置 loop.run_in_executor() 默认执行器

asyncio.run_in_executor 用于在异步事件循环中执行一个阻塞的函数或方法。它将阻塞的调用委托给一个线程池或进程池,以确保不阻塞主事件循环。可以用于在协程中调用一些不支持异步编程的方法,不支持异步编程的模块。

run_in_executor

import asyncio
import concurrent.futures def blocking_function():
# 模拟一个阻塞的操作
import time
time.sleep(2)
return "阻塞函数返回" async def async_function2():
print("async_function2 start")
await asyncio.sleep(1)
print("async_function2 end") async def async_function():
print("异步函数开始执行。。。") print("调用同步阻塞函数")
# 使用run_in_executor调度执行阻塞函数
result = await loop.run_in_executor(None, blocking_function) print(f"获取同步函数的结果: {result}") # 创建一个事件循环
loop = asyncio.get_event_loop() # 运行异步函数
loop.run_until_complete(asyncio.gather(async_function(), async_function2()))

结果:

异步函数开始执行。。。
调用同步阻塞函数
async_function2 start
async_function2 end
获取同步函数的结果: 阻塞函数返回

结果分析:

通过事件循环执行任务async_function,在async_function中通过loop.run_in_executor调用同步阻塞函数blocking_function,该阻塞函数没有影响事件循环中另一个任务async_function2的执行。

await loop.run_in_executor(None, blocking_function)中None代表使用的是默认线程池,也可以替换成其他线程池。

使用自定义线程池和进程池

import asyncio
import concurrent.futures def blocking_function():
# 模拟一个阻塞的操作
import time
time.sleep(2)
return "阻塞函数返回" async def async_function():
print("异步函数开始执行。。。") print("调用同步阻塞函数") # 线程池
with concurrent.futures.ThreadPoolExecutor() as pool:
result = await loop.run_in_executor(
pool, blocking_function)
print('线程池调用返回结果:', result) # 进程池
with concurrent.futures.ProcessPoolExecutor() as pool:
result = await loop.run_in_executor(
pool, blocking_function)
print('进程池调用返回结果:', result) if __name__ == '__main__':
# 创建一个事件循环
loop = asyncio.get_event_loop() # 运行异步函数
loop.run_until_complete(async_function())

结果:

异步函数开始执行。。。
调用同步阻塞函数
线程池调用返回结果: 阻塞函数返回
进程池调用返回结果: 阻塞函数返回

结果分析:

通过线程池concurrent.futures.ThreadPoolExecutor()和进程池concurrent.futures.ProcessPoolExecutor()执行阻塞函数。

任务与期程

函数 功能
loop.create_future() 创建一个 Future 对象。
loop.create_task() 将协程当作 Task 一样调度。
loop.set_task_factory() 设置 loop.create_task() 使用的工厂,它将用来创建 Tasks 。
loop.get_task_factory() 获取 loop.create_task() 使用的工厂,它用来创建 Tasks 。

create_future

create_future 的功能是创建一个future对象。future对象通常不需要手动创建,因为task会自动管理任务结果。相当于task是全自动,创建future是半自动。创建的future就需要手动的讲future状态设置成完成,才能表示task的状态为完成。

import asyncio

def foo(future, result):
print(f"此时future的状态:{future}")
future.set_result(result)
print(f"此时future的状态:{future}") if __name__ == '__main__':
loop = asyncio.get_event_loop() # 手动创建future对象
all_done = loop.create_future() # 设置一个回调函数用于修改设置future的结果
loop.call_soon(foo, all_done, "Future is done!") result = loop.run_until_complete(all_done) print("返回结果", result)
print("获取future的结果", all_done.result())

结果:

此时future的状态:<Future pending cb=[_run_until_complete_cb() at /Users/lib/python3.10/asyncio/base_events.py:184]>
此时future的状态:<Future finished result='Future is done!'>
返回结果 Future is done!
获取future的结果 Future is done!

结果分析:

future设置结果之后之后,future对象的状态就从pending变成finished状态。如果一个future没有手动设置结果,那么事件循环就不会停止。

create_task

将协程封装成一个task对象,事件循环主要操作的是task对象。协程没有状态,而task是有状态的。

import asyncio 

async def demo(i):
print(f"hello {i}")
await asyncio.sleep(1) def main():
loop = asyncio.get_event_loop() # 将携程封装成task,给事件使用
task = loop.create_task(demo(1)) loop.run_until_complete(task) main()
>>>
hello 1

asyncio.create_task 和 loop.create_task的区别:

两者实现的功能都是一样的,将协程封装成一个task,让协程拥有了生命周期。区别仅仅在于使用的方法。asyncio.create_task 是高阶API,不需要创建事件循环,而loop.create_task需要先创建事件循环再使用该方法。

小结

以上是asyncio低阶API的使用介绍,前一篇是高阶API的使用介绍,用两篇介绍了asyncio常见的函数,以后遇到asyncio相关的代码就不会感到陌生。虽然asyncio是比较复杂的编程思想,但是有了这些函数的使用基础,能够更高效的掌握。

连载一系列关于python异步编程的文章。包括同异步框架性能对比、异步事情驱动原理等。欢迎关注微信公众号第一时间接收推送的文章。

python异步编程之asyncio低阶API的更多相关文章

  1. python异步编程之asyncio

    python异步编程之asyncio   前言:python由于GIL(全局锁)的存在,不能发挥多核的优势,其性能一直饱受诟病.然而在IO密集型的网络编程里,异步处理比同步处理能提升成百上千倍的效率, ...

  2. python异步编程之asyncio(百万并发)

      前言:python由于GIL(全局锁)的存在,不能发挥多核的优势,其性能一直饱受诟病.然而在IO密集型的网络编程里,异步处理比同步处理能提升成百上千倍的效率,弥补了python性能方面的短板,如最 ...

  3. python并发编程之asyncio协程(三)

    协程实现了在单线程下的并发,每个协程共享线程的几乎所有的资源,除了协程自己私有的上下文栈:协程的切换属于程序级别的切换,对于操作系统来说是无感知的,因此切换速度更快.开销更小.效率更高,在有多IO操作 ...

  4. 异步编程之asyncio简单介绍

    引言: python由于GIL(全局锁)的存在,不能发挥多核的优势,其性能一直饱受诟病.然而在IO密集型的网络编程里,异步处理比同步处理能提升成百上千倍的效率,弥补了python性能方面的短板. as ...

  5. Python核心技术与实战——十八|Python并发编程之Asyncio

    我们在上一章学习了Python并发编程的一种实现方法——多线程.今天,我们趁热打铁,看看Python并发编程的另一种实现方式——Asyncio.和前面协程的那章不太一样,这节课我们更加注重原理的理解. ...

  6. python并发编程之gevent协程(四)

    协程的含义就不再提,在py2和py3的早期版本中,python协程的主流实现方法是使用gevent模块.由于协程对于操作系统是无感知的,所以其切换需要程序员自己去完成. 系列文章 python并发编程 ...

  7. python并发编程之multiprocessing进程(二)

    python的multiprocessing模块是用来创建多进程的,下面对multiprocessing总结一下使用记录. 系列文章 python并发编程之threading线程(一) python并 ...

  8. python并发编程之Queue线程、进程、协程通信(五)

    单线程.多线程之间.进程之间.协程之间很多时候需要协同完成工作,这个时候它们需要进行通讯.或者说为了解耦,普遍采用Queue,生产消费模式. 系列文章 python并发编程之threading线程(一 ...

  9. python并发编程之threading线程(一)

    进程是系统进行资源分配最小单元,线程是进程的一个实体,是CPU调度和分派的基本单位,它是比进程更小的能独立运行的基本单位.进程在执行过程中拥有独立的内存单元,而多个线程共享内存等资源. 系列文章 py ...

  10. 异步编程之co——源码分析

    异步编程系列教程: (翻译)异步编程之Promise(1)--初见魅力 异步编程之Promise(2):探究原理 异步编程之Promise(3):拓展进阶 异步编程之Generator(1)--领略魅 ...

随机推荐

  1. dp_ppi转光纤模块连接200PLC组态王通信案例

    DP_PPI转光纤模块连接200PLC组态王光纤通信在冷却塔控制系统案例 现场背景介绍: 西门子200 CPU226PLC通过兴达易控dp转光纤模块在200PLC系统中ppi转光纤实现PCL与组态王2 ...

  2. 【RocketMQ】事务实现原理总结

    RocketMQ事务的使用场景 单体架构下的事务 在单体系统的开发过程中,假如某个场景下需要对数据库的多张表进行操作,为了保证数据的一致性,一般会使用事务,将所有的操作全部提交或者在出错的时候全部回滚 ...

  3. Mybatiplus通用3.5.1版本及其以上的代码生成器工具类

    Mybatiplus通用3.5.1版本及其以上的代码生成器工具类 package com.gton.util; import com.baomidou.mybatisplus.annotation.F ...

  4. Chiplet解决芯片技术发展瓶颈

    这是IC男奋斗史的第38篇原创 本文1776字,预计阅读4分钟. Chiplet封装是什么 介绍Chiplet前,先说下SOC.Chiplet和SOC是两个相互对立的概念,刚好可以用来互为参照. SO ...

  5. 小景的Dba之路--如何导出0记录表以及数据泵的使用

    小景最近在系统压测相关的工作,其中涉及了数据备份导出的操作.今天的问题是:exp命令不会导出0记录表,那么我们探讨下如何导出0记录表以及数据泵的使用. 首先,我们先刨析一下问题现象及原因: 在 Ora ...

  6. APIO 2023 游记

    真心话大冒险很有趣. rand 一个房间去敲门加 QQ 很有趣.这么看社恐猫好像也没那么社恐. 面到了 zpl pcq iee dx.单方面认识了很多神仙. 比赛只会写暴力,评测 queue 害人不浅 ...

  7. shell脚本之规范与变量

    shell编程规范与变量 名词简述 面向过程语言 按照顺序执行程序 第一件事干什么->第二件事干什么......(C,shell...) 面向对象语言 把程序看成一个整体(java,python ...

  8. Net 高级调试之四:Windbg 动态调试

    一.简介 今天是<Net 高级调试>的第四篇文章.到今天为止,也有三篇文章了,对 Windbg 也有初步的认识了,当然,一个工具流畅.熟练的使用,对于我们调试 Net 程序是至关重要的.在 ...

  9. Redis Functions 介绍之一

    Redis提供了编程接口(programming interface)可以让你在Redis服务器端执行客户的脚本. 一个重大的变化就是从Redis 7开始,你可以选择使用Redis Functions ...

  10. MySQL IFNULL函数

    MySQL IFNULL函数简介 MySQL IFNULL函数是MySQL控制流函数之一,它接受两个参数,如果不是NULL,则返回第一个参数. 否则,IFNULL函数返回第二个参数. 两个参数可以是文 ...