Python的可等待对象在Asyncio的作用

本文深入解析Python Asyncio中的可等待对象,包括Coroutine、Task和Future的关系及作用。通过示例代码解释它们如何协同工作,实现协程的并发调用。Task作为事件循环的调度核心,使得协程能够通过Future的状态变化实现并发执行。同时,讨论了直接调用Coroutine与通过Task调用的差异,以及Task的创建和管理在并发执行中的关键作用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Python Asyncio通过Future和Task的封装来实现协程的调度,而在Python Asyncio之中Coroutines, Tasks和Future都属于可等待对象,在使用的Asyncio的过程中,经常涉及到三者的转换和调度,开发者容易在概念和作用上犯迷糊,本文主要阐述的是三者之间的关系以及他们的作用。

1.Asyncio的入口

协程是线程中的一种特例,协程的入口和切换都是靠事件循环来调度的,在新版的Python中协程的入口是Asyncio.run,当程序运行到Asyncio.run后,可以简单的理解为程序由线程模式切换为协程模式(只是方便理解,对于计算机而言,并没有这样区分),以下是一个最小的协程例子代码:

import asyncio


async def main():
    await asyncio.sleep(0)

asyncio.run(main())

在这段代码中,main函数和asyncio.sleep都属于Coroutine,main是通过asyncio.run进行调用的,接下来程序也进入一个协程模式,asyncio.run的核心调用是Runner.run,它的代码如下:

class Runner:
    ...

    def run(self, coro, *, context=None):
        """Run a coroutine inside the embedded event loop."""
        # 省略代码
        ...

        # 把coroutine转为task
        task = self._loop.create_task(coro, context=context)

        # 省略代码
        ...

        try:
            # 如果传入的是Future或者coroutine,也会专为task
            return self._loop.run_until_complete(task)
        except exceptions.CancelledError:
        
        # 省略代码
        ...

这段代码中删去了部分其它功能和初始化的代码,可以看到这段函数的主要功能是通过loop.create_task方法把一个Coroutine对象转为一个Task对象,然后通过loop.run_until_complete等待这个Task运行结束。

Python3.5之后,asycnio改为又C语言实现,所以本文是asyncio源码都来源于最后一个以Python实现的asycnio版本Python3.4.10/Lib/asyncio[2]

可以看到,Asycnio并不会直接去调度Coroutine,而是把它转为Task再进行调度,这是因为在Asyncio中事件循环的最小调度对象就是Task。不过在Asyncio中并不是所有的Coroutine的调用都会先被转为Task对象再等待,比如示例代码中的asyncio.sleep,由于它是在main函数中直接await的,所以它不会被进行转换,而是直接等待,在这个图示中,从main函数到asyncio.sleep函数中没有明显的loop.create_task等把Coroutine转为Task调用,这里之所以不用进行转换的原因不是做了一些特殊优化,而是本因如此, 这个await asyncio.sleep函数实际上还是会被main这个Coroutine转换成的Task继续调度到。

2.两种Coroutine调用方法的区别

在了解Task的调度原理之前,还是先回到最初的调用示例,看看直接用Task调用和直接用Coroutine调用的区别是什么。如下代码,我们显示的执行一个Coroutine转为Task的操作再等待,那么代码会变成下面这样:

import asyncio


async def main():
    await asyncio.create_task(asyncio.sleep(0))

asyncio.run(main())

这样的代码看起来跟最初的调用示例很像,没啥区别,但是如果进行一些改变,比如增加一些休眠时间和Coroutine的调用,就能看出Task对象的作用了,现在编写两份文件,他们的代码如下:

# demo_coro.py
import asyncio
import time


async def main():
    await asyncio.sleep(1)
    await asyncio.sleep(2)

s_t = time.time()
asyncio.run(main())
print(time.time() - s_t)
# // Output: 3.0028765201568604

# demo_task.py
import asyncio
import time


async def main():
    task_1 =
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值