简单有效上手Python3异步asyncio问题

目录
  • Python3异步asyncio问题
    • 更新
  • 下面是学习过程中记录的偏低层实现的资料
    • 最基本的定义和应用
    • 什么时候使用异步
    • 并发和并行
    • 异步结果回调
  • 总结

Python3异步asyncio问题

官方文档:

https://docs.python.org/zh-cn/3/library/asyncio-task.html#asyncio.run

看了一大堆相关的资料和教程,针对的Python版本不同,写法也各不一致,翻了翻官方的文档,发现其实越高版本的Python对异步进行封装的越方便,官方说法叫高层级API,甚至都不用去理解什么Future\task\loop之类的概念了,我现在用的是Python 3.7.5,可以这样很简单的实现阻塞等待\异步并发:如果没有复杂需求的话,用高层级API就可以了。

如果涉及到回调的话貌似还得用低层级的API,后面单独记录。

import asyncio

async def first():
    print('first函数调用开始,下面将会等待3秒模拟函数运行完毕')
    await asyncio.sleep(3)
    print('first函数执行完毕')

async def last():
    print('last函数调用开始')
    await asyncio.sleep(2)
    print('last函数执行完毕')

async def func(delay):
    print('开始异步同时执行的函数+延迟: ' + str(delay))
    await asyncio.sleep(delay)
    print('--异步函数执行完毕+延迟: ' + str(delay))

async def main():
    await first()  # 这里先调用first()函数,并且等它执行完了才会开始
    await asyncio.gather(
        func(1),
        func(2),
        func(3)
    )
    await last()

asyncio.run(main())

上面代码实际执行的过程是:

  • 开始执行first()函数
  • first()执行完毕后开始并发执行下面gather中加入的
  • 三个函数(任务)三个函数全部并发执行完毕后执行last()

官方文档中给的建议是只创建一个主入口的main()函数(当然这个函数名可以自定义的),将要调用的其他函数都放在这个函数中,然后再使用asyncio.run()启动,理想情况下应当只被调用一次.

上图:

更新

上面所谓的高阶层API用法最后一行asyncio.run(main())和下面使用低阶层API实现效果是一样的:

loop = asyncio.get_event_loop()
task = loop.create_task(main())
loop.run_until_complete(task)

下面是学习过程中记录的偏低层实现的资料

最基本的定义和应用

import asyncio

# 定义一个可以异步调用的函数,其类型为coroutine
async def func1():
    pass

if __name__ == '__main__':
    loop = asyncio.get_event_loop()	# 定义一个用来循环异步函数的loop对象
    task = asyncio.ensure_future(func1())	# 创建一个调用异步函数的任务,task类型为future
    # task = loop.create_task(func1())	# 使用loop的.create_task()创建任务,效果一样
    loop.run_until_complete(task)	# 开始在loop循环中执行异步函数,直到该函数运行完毕

asyncio.ensure_future(coroutine)loop.create_task(coroutine)都可以创建一个taskrun_until_complete的参数是一个futrue对象。

当传入一个协程,其内部会自动封装成task,task是Future的子类。

isinstance(task, asyncio.Future)将会输出True。

future类型的任务可以在loop.run_until_complete中执行,也可以直接用await+任务变量名阻塞?调用

什么时候使用异步

import asyncio

# 这是一个耗时很少的异步函数
async def msg(text):
    await asyncio.sleep(0.1)
    print(text)

# 这是一个耗时较长的异步函数
async def long_operation():
    print('long_operation started')
    await asyncio.sleep(3)
    print('long_operation finished')

# 主函数部分,同样需要声明为async异步类型
async def main():
    await msg('first')
    # 现在需要调用一个耗时较长的函数操作,不希望阻塞的等待它执行完毕
    # 希望long_operation在开始执行后,立即调用msg,这里就可以将long_operation封装到task任务中
    task = asyncio.ensure_future(long_operation())
    await msg('second')
    # 开始task中的long_operation函数
    await task
    # task执行完毕后会继续下面的代码
    print('All done.')

if __name__ == "__main__":
    loop = asyncio.get_event_loop()
    loop.run_until_complete(main())

运行结果:

并发和并行

并发和并行一直是容易混淆的概念。并发通常指有多个任务需要同时进行,并行则是同一时刻有多个任务执行。

用上课来举例就是,并发情况下是一个老师在同一时间段辅助不同的人功课。

并行则是好几个老师分别同时辅助多个学生功课。

简而言之就是一个人同时吃三个馒头还是三个人同时分别吃一个的情况,吃一个馒头算一个任务。

asyncio实现并发,就需要多个协程来完成任务,每当有任务阻塞的时候就await,然后其他协程继续工作。

创建多个协程的列表,然后将这些协程注册到事件循环中。

import asyncio

import time

now = lambda: time.time()

async def do_some_work(x):
    print('Waiting: ', x)

    await asyncio.sleep(x)
    return 'Done after {}s'.format(x)

start = now()

coroutine1 = do_some_work(1)
coroutine2 = do_some_work(2)
coroutine3 = do_some_work(4)

tasks = [
    asyncio.ensure_future(coroutine1),
    asyncio.ensure_future(coroutine2),
    asyncio.ensure_future(coroutine3)
]

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

for task in tasks:
    print('Task ret: ', task.result())

print('TIME: ', now() - start)

结果如下

Waiting:  1
Waiting:  2
Waiting:  4
Task ret:  Done after 1s
Task ret:  Done after 2s
Task ret:  Done after 4s
TIME:  4.003541946411133

总时间为4s左右。4s的阻塞时间,足够前面两个协程执行完毕。如果是同步顺序的任务,那么至少需要7s。此时我们使用了aysncio实现了并发。

asyncio.wait(tasks) 也可以使用 asyncio.gather(*tasks) ,前者接受一个task列表,后者接收一堆task。

异步结果回调

找了个别人写的例子,大致理解了下实现过程:

  • 定义async异步函数
  • 定义普通函数用于处理回调
  • 获取异步函数的coroutine协程对象(其实就是不带await修饰直接执行异步函数返回的那个对象)
  • 获取loop循环对象
  • 使用低阶层API手动创建task任务
  • task任务对象注册回调函数
  • 启动loop循环调用异步函数
import time
import asyncio

now = lambda : time.time()

async def do_some_work(x):
    print('Waiting: ', x)
    return 'Done after {}s'.format(x)

def callback(future):
    print('Callback: ', future.result())

start = now()

coroutine = do_some_work(2)
loop = asyncio.get_event_loop()
task = loop.create_task(coroutine)
#task = asyncio.ensure_future(coroutine)	# 貌似和上面用loop创建任务效果一样
task.add_done_callback(callback)
loop.run_until_complete(task)

print('TIME: ', now() - start)

这里需要注意,在使用低层级API手动创建异步任务的时候,不能同时使用高层级API的简单操作了,比如这里创建task任务对象的时候,就不能用asyncio.create_task(),否则会找不到loop对象,返回下面的错误

RuntimeError: no running event loop

翻了一下asyncio\tasks.py源代码,原来所谓的高层级API就是这么简单的封装啊…

调用的时候在函数内部又定义了一遍loop

def create_task(coro):
    """Schedule the execution of a coroutine object in a spawn task.

    Return a Task object.
    """
    loop = events.get_running_loop()
    return loop.create_task(coro)

我自己写的例子

创建4个异步任务同时开始执行,每个任务执行完成后将结果追加到result_list数组变量中.

import asyncio

result_list = []

async def fun(var):
    return var + 1

def callbackFun(future):
    result_list.append(future.result())

task_list = []

for i in range(1, 5):
    cor = fun(i)
    task = asyncio.ensure_future(cor)
    task.add_done_callback(callbackFun)
    task_list.append(task)

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

当然,如果不需要使用回调函数,而是等所有提交的异步任务都执行完成后获取它们的结果,可以这样写:

# 前面省略
loop.run_until_complete(asyncio.wait(task_list))
# task_list中的每个任务执行完成后可以调用它的.result()方法来获取结果
for task in task_list:
    print('每个task执行完的结果:', task.result())

总结

以上为个人经验,希望能给大家一个参考,也希望大家多多支持我们。

(0)

相关推荐

  • Python Asyncio库之asyncio.task常用函数详解

    目录 前记 0.基础 1.休眠--asyncio.sleep 2.屏蔽取消--asyncio.shield 3.超时--asyncio.wait_for 4.简单的等待--wait 5.迭代可等待对象的完成--asyncio.as_completed 前记 Asyncio在经过一段时间的发展以及获取Curio等第三方库的经验来提供更多的功能,目前高级功能也基本完善,但是相对于其他语言,Python的Asyncio高级功能还是不够的,但好在Asyncio的低级API也比较完善,开发者可以通过参考A

  • Python asyncio常用函数使用详解

    目录 协程的定义 协程的运行 多个协程运行 关于loop.close() 回调 事件循环 协程的定义 需要使用 async def 语句 协程可以做哪些事: 1.等待一个future结果 2.等待另一个协程(产生一个结果或引发一个异常) 3.产生一个结果给正在等它的协程 4.引发一个异常给正在等它的协程 协程的运行 调用协程函数,协程不会开始运行,只是返回一个协程对象 要让协程对象运行有两种方式: 1.在另一个已经运行的协程中用await等待它 2.通过ensure_future函数计划它的执行

  • Python asyncio异步编程简单实现示例

    目录 一.asyncio事件循环简介 二.async协程函数简介 三.await关键字 四.async异步编程简单实现 今天继续给大家介绍Python相关知识,本文主要内容是Python asyncio异步编程简单实现. 一.asyncio事件循环简介 asyncio引入了事件循环的概念.事件循环是一个死循环,还循环会检测并执行某些代码.在Python中,引入了asyncio模块后,执行命令: loop=asyncio.get_event_loop() 可以生成一个事件循环,而执行命令: loo

  • Python asyncio异步编程常见问题小结

    目录 一.asyncio编程简单示例 二.asyncio编程常见问题 三.报错原因及解决方案 今天继续给大家介绍Python相关知识,本文主要内容是Python asyncio异步编程常见问题. 一.asyncio编程简单示例 首先,我们来看一段简单的Python asyncio异步编程代码,相关代码如下所示: import asyncio async def fun(): print(1) await asyncio.sleep(2) print(2) return 3 async def m

  • Python Asyncio 库之同步原语常用函数详解

    目录 前记 0.基础 1.Lock 2.Event 4.Condition 5.Semaphore 前记 Asyncio的同步原语可以简化我们编写资源竞争的代码和规避资源竞争导致的Bug的出现. 但是由于协程的特性,在大部分业务代码中并不需要去考虑资源竞争的出现,导致Asyncio同步原语被使用的频率比较低,但是如果想基于Asyncio编写框架则需要学习同步原语的使用. 0.基础 同步原语都是适用于某些条件下对某个资源的争夺,在代码中大部分的资源都是属于一个代码块,而Python对于代码块的管理

  • Python使用asyncio包处理并发的实现代码

    使用 asyncio 包处理并发 asyncio包:使用事件循环驱动的协程实现并发. 线程与协程的对比 '\ thinking' 旋转等待效果 In [1]: import threading In [2]: import itertools In [3]: import time,sys In [4]: class Signal: # 定义一个简单的可变对象:go 属性 从外部控制线程 ...: go = True In [5]: def spin(msg,signal): ...: w,fl

  • Python Asyncio调度原理详情

    目录 前言 1.基本介绍 2.EventLoop的调度实现 3.网络IO事件的处理 前言 在文章<Python Asyncio中Coroutines,Tasks,Future可等待对象的关系及作用>中介绍了Python的可等待对象作用,特别是Task对象在启动的时候可以自我驱动,但是一个Task对象只能驱动一条执行链,如果要多条链执行(并发),还是需要EventLoop来安排驱动,接下来将通过Python.Asyncio库的源码来了解EventLoop是如何运作的. 1.基本介绍 Python

  • Python使用Asyncio进行web编程方法详解

    目录 前言 什么是同步编程 什么是异步编程 ayncio 版 Hello 程序 如何使用 asyncio 总结 前言 许多 Web 应用依赖大量的 I/O (输入/输出) 操作,比如从网站上下载图片.视频等内容:进行网络聊天或者针对后台数据库进行多次查询.数据库查询可能会耗费大量时间,尤其是在该数据库处于高负载或查询很复杂的情况下. Web 服务器可能需要同时处理数百或数千个请求. I/O 是指计算机的输入和输出设备,例如键盘.硬盘驱动器,以及最常见的网卡.这些操作等待用户输入或从基于 Web

  • AJAX实现简单的注册页面异步请求实例代码

    AJAX简介 (1)AJAX = 异步 JavaScript 和 XML. (2)AJAX 是一种用于创建快速动态网页的技术. (3)通过在后台与服务器进行少量数据交换,AJAX 可以使网页实现异步更新.这意味着可以在不重新加载整个网页的情况下,对网页的某部分进行更新. (4)传统的网页(不使用 AJAX)如果需要更新内容,必需重载整个网页面.  简单布局 JS先判断,把前端可以的判断做,减少服务器的交互 $('button').on('click',function(){; var boolu

  • Python超简单容易上手的画图工具库(适合新手)

    前言 今天,在网上发现一款很棒的python画图工具库.很简单的api调用就能生成漂亮的图表.并且可以进行一些互动. pyecharts 是一个用于生成 Echarts 图表的类库.Echarts 是百度开源的一个数据可视化 JS 库.用 Echarts 生成的图可视化效果非常棒.废话不多说下来直接看效果(对于我这种没审美感的人来是我觉得挺漂亮的). 使用之前需要安装一下:安装命令很简单:Pip就可以安装: 这里我安装在我的虚拟环境中了:pip install pyecharts . 官方的文档

  • Python超简单容易上手的画图工具库推荐

    今天,在网上发现一款很棒的python画图工具库.很简单的api调用就能生成漂亮的图表.并且可以进行一些互动. pyecharts 是一个用于生成 Echarts 图表的类库.Echarts 是百度开源的一个数据可视化 JS 库.用 Echarts 生成的图可视化效果非常棒.废话不多说下来直接看效果(对于我这种没审美感的人来是我觉得挺漂亮的). 使用之前需要安装一下:安装命令很简单:Pip就可以安装: 这里我安装在我的虚拟环境中了:pip install pyecharts . 官方的文档和de

  • Android Studio实现音乐播放器的全过程(简单易上手)

    目录 前言 一.项目概述 1.需求分析 2.设计分析 3.资源文件分析 二.开发环境 三.准备工具 四.详细设计 1.搭建主界面布局 2.创建服务类 2.1.服务概述 2.2.服务的创建 2.3.服务的启动方式 2.4.服务的生命周期 3.搭建音乐播放界面布局 4.搭建音乐列表界面布局 5.搭建专辑界面布局 6.导入资源文件 五.项目效果 1.创建模拟器 2.运行演示 六.项目总结 附如何将图片剪成圆形 前言 我们大家平时长时间打代码的时候肯定会感到疲惫和乏味,这个时候一边播放自己喜欢的音乐,一

  • node+express+ejs制作简单页面上手指南

    1.建立工程文件夹my_ejs. 2.首先利用npm install express和npm install ejs 下载这两个家伙.至于要不要设置成全局的,看习惯,我习惯性的下载到本项目中的文件夹中my_ejs. 然后建立相应的文件: index.js: form.ejs: index.ejs app.js: 开始运行app.js node app.js,然后再浏览器端访问:localhost:1337 单击发表文章: 点击发表,跳转到首页. 好了到此为止,一个简易的"网站"算是出来

  • Elasticsearch Join字段类型简单快速上手教程

    目录 概述 父子关系的限制 Global ordinals 父子文档 总结 阅读本文需要一定的Elasticsearch基础哦,本文深度有,但是不深 概述 Elasticsearch中Join数据类型的字段相信大家也都用过,也就是口中常谈的父子文档.在Elasticsearch中Join不能跨索引和分片,所以保存文档信息时要保证父子文档使用相同的路由参数来保证父文档与子文档保存在同一个索引的同一个分片,那么都有哪些限制呢? 父子关系的限制 每个索引中只能有一个关系字段 父文档与子文档必须在同一个

  • python3爬虫中异步协程的用法

    1. 前言 在执行一些 IO 密集型任务的时候,程序常常会因为等待 IO 而阻塞.比如在网络爬虫中,如果我们使用 requests 库来进行请求的话,如果网站响应速度过慢,程序一直在等待网站响应,最后导致其爬取效率是非常非常低的. 为了解决这类问题,本文就来探讨一下 Python 中异步协程来加速的方法,此种方法对于 IO 密集型任务非常有效.如将其应用到网络爬虫中,爬取效率甚至可以成百倍地提升. 注:本文协程使用 async/await 来实现,需要 Python 3.5 及以上版本. 2.

  • Python 异步之在 Asyncio中如何运行阻塞任务详解

    目录 正文 1. 阻塞任务 2. 如何运行阻塞任务 3. 实例 正文 阻塞任务是阻止当前线程继续进行的任务. 如果在 asyncio 程序中执行阻塞任务,它会停止整个事件循环,从而阻止任何其他协程继续进行. 我们可以通过 asyncio.to_thread() 和 loop.run_in_executor() 函数在 asyncio 程序中异步运行阻塞调用. 1. 阻塞任务 asyncio的重点是异步编程和非阻塞IO.然而,我们经常需要在 asyncio 应用程序中执行阻塞函数调用. 这可能有很

  • Python3.6正式版新特性预览

    按照Python官网上的计划,Python3.6正式版期望在2016-12-16号发布,也就是这周五.从去年的5月份开始,Python3.6版本就已经动手开发了,期间也断断续续的发布了4个Alpha版,4个Beta版,以及一个Candidate版本. 作为一个Python爱好者,很期待新版本的发布,也希望能第一时间尝试一下新特性.本文就根据Python官网文章What's New In Python 3.6,简单介绍下Python3.6中的一些新特性. 如果你想尝试Python3.6,又不想破坏

随机推荐