Python协程还不理解?请收下这份超详细的异步编程教程( 二 )

  • coroutine:中文翻译叫协程,在 Python 中常指代为协程对象类型,我们可以将协程对象注册到事件循环中,它会被事件循环调用 。我们可以使用 async 关键字来定义一个方法,这个方法在调用时不会立即被执行,而是返回一个协程对象 。
  • task:任务,它是对协程对象的进一步封装,包含了任务的各个状态 。
  • future:代表将来执行或没有执行的任务的结果,实际上和 task 没有本质区别 。
  • 另外我们还需要了解 async/await 关键字,它是从 Python 3.5 才出现的,专门用于定义协程 。其中,async 定义一个协程,await 用来挂起阻塞方法的执行 。
    Python协程还不理解?请收下这份超详细的异步编程教程

    文章插图
     
    2.1 定义协程协程就是一个函数,只是它满足以下几个特征:
    • 依赖 I/O 操作(有 I/O 依赖的操作)
    • 可以在进行 I/O 操作时暂停
    • 无法直接运行
    它的作用就是对有大量 I/O 操作的程序进行加速 。
    Python 协程属于可等待对象,因此可以在其他协程中被等待 。
    什么叫可等待对象?——await,如果前面被标记 await 就表明他是个协程,我们需要等待它返回一个数据 。
    Python协程还不理解?请收下这份超详细的异步编程教程

    文章插图
     
    举个例子,我从网络上下载某个数据文件下载到我的本地电脑上,这很显然是一个 I/O 操作 。比方这个文件较大(2GB),可能需要耗时 30min 才能下载成功 。而在这 30min 里面,它会卡在 await 后面 。这个 await 标记了协程,那就意味着它可以被暂停,那既然该任务可以被暂停,我们就把它分离出去 。我这个线程继续执行其它任务,它这个 30min 分出去慢慢的传输,我这个程序再运行其他操作 。
    上面的代码,Python 3.6 会给你报错 。报错信息如下:
    Python协程还不理解?请收下这份超详细的异步编程教程

    文章插图
     
    为什么会出现这样的报错呢?
    因为从 Python 3.7+ 之后 Python 已经完全支持异步了,Python 3.6 之前只是支持部分异步,许多的方法是非常冗长的 。
    一个异步函数调用另一个异步函数:
    tips:
    异步主要做得是 I/O 类型,CPU 密集型就不需要使用异步 。
    一个异步调用另一个异步函数,不能直接被调用,必须添加 await
    我们使用代码验证一下,不加 await 调用试一试:
    Python协程还不理解?请收下这份超详细的异步编程教程

    文章插图
     
    输出结果:
    Python协程还不理解?请收下这份超详细的异步编程教程

    文章插图
     
    我们添加上 await 即可正常运行:
    Python协程还不理解?请收下这份超详细的异步编程教程

    文章插图
     
    运行结果:
    Python协程还不理解?请收下这份超详细的异步编程教程

    文章插图
     
    运行成功并没有报错,接下来我们要输出得到的结果该怎么编写代码呢?直接赋值即可:
    Python协程还不理解?请收下这份超详细的异步编程教程

    文章插图
     
    Ps:async 标记异步,await 标记等待 。
    如果我们不想使用 await 来运行异步函数,那这个时候我们就可以按如下方法来运行代码:
    Python协程还不理解?请收下这份超详细的异步编程教程

    文章插图
     
    首先我们来定义一个协程,体验一下它和普通进程在实现上的不同之处,代码如下:
    Python协程还不理解?请收下这份超详细的异步编程教程

    文章插图
     
    代码示例二中,我们首先引入了 asyncio这个包,这样我们才可以使用 async和 await,然后我们使用 async定义了一个 execute方法,方法接收一个数字参数,方法执行之后会打印这个数字 。
    随后我们直接调用了这个方法,然而这个方法并没有执行,而是返回了一个 coroutine协程对象 。随后我们使用 get_event_loop方法创建了一个事件循环 loop,并调用了 loop对象的 run_until_complete方法将协程注册到事件循环 loop中,然后启动 。最后我们才看到了 execute方法打印了输出结果 。
    可见,async定义的方法就会变成一个无法直接执行的 coroutine对象,必须将其注册到事件循环中才可以执行 。
    上面我们还提到了 task,它是对 coroutine 对象的进一步封装,它里面相比 coroutine 对象多了运行状态,比如 running、finished 等,我们可以用这些状态来获取协程对象的执行情况 。
    在上面的例子中,当我们将 coroutine 对象传递给 run_until_complete 方法的时候,实际上它进行了一个操作就是将 coroutine 封装成了 task 对象,我们也可以显式地进行声明,如下所示:


    推荐阅读