专栏首页数据科学实战Python异步与 JavaScript 原生异步有什么区别?

Python异步与 JavaScript 原生异步有什么区别?

众所周知,JavaScript 是单线程的,所以浏览器通过 JavaScript 发起的请求是异步请求。Python 自带的 asyncio 模块为 Python 带来了原生的异步能力。

在学习 asyncio 时,我们应当正确认识到异步代码在 Python 中与 JavaScript 原生代码中有什么区别,这样才能更好地理解Python中用同步代码写异步程序这个逻辑。

对于异步操作,我们如果使用日常生活中的例子,可能会帮助我们理解 JavaScript 原生的异步操作,但是却有可能阻碍我们理解 Python 的异步操作。

例如:我把洗衣机打开,等待洗衣机自动运行的这段时间,我可以去煮饭,等待饭煮好的这个过程,我可以去看书。

现在假设我们要请求一个网址:http://httpbin.org/delay/5,这个网址请求以后,需要等待5秒钟才会返回结果。我们使用 jQuery来写一段 JavaScript 代码:

function test_async(){
    $.ajax({type: 'GET',
            contentType: 'application/json; charset=utf-8',
            url: 'http://httpbin.org/delay/5',
            success: function (response) {
                console.log('5秒请求返回:', response)

            }
        })
    var a = 1 + 1
    a = a * 2
    console.log(a)
    $.ajax({type: 'GET',
            contentType: 'application/json; charset=utf-8',
            url: 'http://httpbin.org/ip',
            success: function (response) {
                console.log('查询 IP 返回:', response)

            }
        })
    console.log('这里是代码的末尾')
}

运行效果如下图所示:

可以看出来,整个代码的执行逻辑与我们生活中的异步是一致的,首先发起了一个5秒的请求,但是程序不会卡住等待,而是继续运行后面的代码,然后发起新的请求。由于新的请求返回时间短,所以新的请求很快返回并打印,最后才是打印的5秒请求的返回结果。

这就像是我们打开了洗衣机的电源,然后去淘米煮饭,米放进了电饭锅,打开电饭锅电源,然后去看书,最后饭先煮好,然后衣服再洗完。

JavaScript 原生的异步请求的过程,与日常生活中的逻辑很像。所以很容易就能理解 JavaScript 的异步流程。

但是 Python 里面,异步又是另外一种情况了。

我们来写一段代码:

import asyncio
import aiohttp

async def main():
    async with aiohttp.ClientSession() as client:
        response = await client.get('http://httpbin.org/delay/5')
        result = await response.json()
        print('5秒请求返回:', result)
        a = 1 + 1
        a = a * 2
        print(a)
        new_response = await client.get('http://httpbin.org/ip')
        new_result = await new_response.json()
        print('查询 IP 返回:', new_result)
        print('这里是代码的末尾')

asyncio.run(main())

运行效果如下图所示:

可以看出,程序依然是串行运行的,根本就没有异步痕迹。

要让程序异步运行,我们需要凑够一批任务提交给 asyncio,让它自己通过事件循环来调度这些任务:

import asyncio
import aiohttp


async def do_plus():
    a = 1 + 1
    a = a * 2
    print(a)


async def test_delay(client):
    response = await client.get('http://httpbin.org/delay/5')
    result = await response.json()
    print('5秒请求返回:', result)


async def test_ip(client):
    response = await client.get('http://httpbin.org/ip')
    result = await response.json()
    print('查询 IP 返回:', result)


async def test_print():
    print('这里是代码的末尾')


async def main():
    async with aiohttp.ClientSession() as client:
        tasks = [
                asyncio.create_task(test_delay(client)),
                asyncio.create_task(do_plus()),
                asyncio.create_task(test_ip(client)),
                asyncio.create_task(test_print())
                ]
        await asyncio.gather(*tasks)

asyncio.run(main())

运行效果如下图所示:

这是由于,在asyncio 里面,task是可以并行的最小单位,并且,task 要凑够一批一起通过asyncio.gather或者asyncio.wait提交给事件循环以后,才能并行起来。

当使用代码asyncio.create_task(异步函数())的时候,这个异步函数实际上并没有真正运行,所以,在上面的代码中:

tasks = [
                asyncio.create_task(test_delay(client)),
                asyncio.create_task(do_plus()),
                asyncio.create_task(test_ip(client)),
                asyncio.create_task(test_print())
                ]

创建了一个包含4个task 的列表,此时这4个异步函数中的代码都还没有执行。

当再调用await asyncio.gather(*tasks)时,这4个任务被作为4个参数传入到了 asyncio.gather函数中,于是 Python 的事件循环开始调度他们。在这些异步函数中,包含await的地方,就是在告诉 Python,await后面的这个函数可能会有 IO 等待,可以挂起等一会再来看,现在可以去检查事件循环里面其他异步任务是否已经结束等待可以运行。而没有 await的地方依然是串行的,例如do_plus里面的三行代码就是按顺序一次性运行完成的。

所以,当我们使用 Python 的 asyncio 写异步代码时,我们需要提前安排好异步的切换位置并包装为异步任务,然后把一批任务一次性提交给 asyncio,让 Python 自己根据我们安排好的切换逻辑来调度这些任务。

这就像是,当我写 JavaScript 的时候,我亲自上阵先把洗衣机电源打开,然后我再来考虑接下来要利用等待时间做什么事情。

当我写 Python 的时候,我需要提前把整个计划都安排好:先打开洗衣机,在等待的时间淘米煮饭,然后再看书。并把这个计划表提交给一个专门做事情的人来执行。

理解了这个差别,才能更好地在 Python 中使用 asyncio。

注意,本文说到的 JavaScript异步,是 JavaScript 最原始的异步逻辑。现在 JavaScript 有 Promise 等等高级功能,实现类似于 Python 的这种异步逻辑。

本文分享自微信公众号 - 数据科学实战(quanter001)

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2020-03-24

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • AkShare-近期修复部分接口-20200312

    最近随着小伙伴的增加,AkShare 接口中的部分不合理的设计和接口使用中的问题陆续都被暴露出来,也通过这些问题从而提高了 AkShare 数据获取的易用性、代...

    AkShare
  • AkShare-另类数据-空气质量

    目标地址: https://www.zq12369.com/environment.php

    AkShare
  • AkShare-宏观数据-中国货币供应量

    补充之前的中国 M2 接口,本次接口增加 M1 和 M0 的月度数据,数据从 200801 至今。

    AkShare
  • busybox syslog介绍

    可通过设置/etc/syslog.conf具体配置不同log的处理规则,以下的简单配置,将log全部写到/var/log/messages和控制台

    zqb_all
  • 微软超融合私有云测试25-SCOM2016部署之配置服务监控与自愈

    SCOM支持服务级别的监控,并可针对某些重要服务配置服务停止后自动启动,综合来讲SCOM是一个很强大的平台,除了服务外,还可实现某些基于程序进程的应用自我修复或...

    SuperDream
  • css教程之文本字体

    <integer>:100 | 200 | 300 | 400 | 500 | 600 | 700 | 800 | 900

    老雷PHP全栈开发
  • Chrome开发者工具Debug入门

    作为一个刚入门的开发者,找到 BUG 的根源并修复通常要花费不少功夫。往往会尝试在代码中随机用console.log打印变量值来寻找问题。

    Fundebug
  • 产品专员面试流程及经验(附HR点评)

    张树臣
  • markdown基础语法

    一个淡定的打工菜鸟
  • pageadmin CMS网站制作教程:实例:如何制作一个报名表?

    pageadmin CMS网站建设教程:实例:如何制作一个报名表? 有时我们根据需求需要制作一些自定义表,该如何去制作呢?

    Almost Lover

扫码关注云+社区

领取腾讯云代金券