asyncio中await关键字如何使用

1. await的作用

async def main():
    ...                    # ①
    result = await xxx()   # ②
    ...                    # ③
    result2 = await yyy()  # ④

await类似yield from,程序main()执行到await表达式处(②),main()会在②处阻塞,③④所在程序无法继续执行下去,直到xxx()执行完并返回结果。

xxx()执行完并返回结果后,返回结果赋值给变量result,然后main()继续执行下去。

await的作用就是阻塞调用方,并获取await xxx()中xxx的返回值。

await好像就是一种控制流程,下面if语句进行不严谨类比。

def main():
    ...
    _r = None
    if True:        # ①
        _r = xxx()  # ②
    result = _r     # ③
    ...             # ④

程序执行到①处,就会在if分支里面(②)执行,③处的程序只在执行②执行完了,才能继续。

await就类似于if True了,await xxx()中的xxx()就类似if语句里面逻辑了。

2. 如何在协程中使用await?

# 程序一

import time
import asyncio
from collections import namedtuple

Result = namedtuple('Result', 'name, result')

async def factorial(name, n):
    f = 1
    for i in range(1, n + 1):
        f *= i
    await asyncio.sleep(n)
    print(f'> {time.strftime("%X")} - ({name}) Factorial({n})={f}')
    return Result(name, f)

async def main():
    print(f'=== start time: {time.strftime("%X")}')
    r = await factorial('fact-1', 3)
    print(f'Second time:{time.strftime("%X")}, Result: <{r.name}, {r.result}>')

    r = await factorial('fact-2', 5)
    print(f'=== Third time:{time.strftime("%X")}, Result: <{r.name}, {r.result}>')

# if __name__ == '__main__':  在非Jupyter上应该使用这个
#     asyncio.run(main())
await main()  # 在Jupyter上执行

执行结果:

=== start time: 22:57:35
> 22:57:38 - (fact-1) Factorial(3)=6
Second time:22:57:38, Result: <fact-1, 6>
> 22:57:43 - (fact-2) Factorial(5)=120
=== Third time:22:57:43, Result: <fact-2, 120>
# 程序二

async def main():
    print(f'=== start time: {time.strftime("%X")}')
    r = await asyncio.create_task(factorial('fact-1', 3))
    print(f'Second time:{time.strftime("%X")}, Result: <{r.name}, {r.result}>')

    r = await asyncio.create_task(factorial('fact-2', 5))
    print(f'=== Third time:{time.strftime("%X")}, Result: <{r.name}, {r.result}>')

await main()

执行结果:

=== start time: 22:40:07
> 22:40:10 - (fact-1) Factorial(3)=6
Second time:22:40:10, Result: <fact-1, 6>
> 22:40:15 - (fact-2) Factorial(5)=120
=== Third time:22:40:15, Result: <fact-2, 120>

程序一和程序二的执行结果都说明了await 阻塞作用,只有执行完await表达式里的协程factorial()main()函数才能继续执行下面的代码。

上面两个程序,去掉asyncawait 关键字,执行的效果同平时写的同步代码是一样的。

所以说,如果不是为了并发,使用async/await的效果等同于用同步方式写的,还增加代码的理解难度。

程序二中,asyncio.create_task()方法是可以用来并发运行的。

3. await在运行并发任务上的如何使用

await关键字使用上的疑惑和纠结,就是我要并发,但await这个关键字我该怎么使用?在何处用?

想要并发,但await的作用却是阻塞程序,并发和await这两个就是矛盾体。没写好,就写成同步去了… 类似程序二,明明create_task()用来并发,执行结果却没效果。

await在何处使用关乎并发的效果。

# 程序三

async def main():
    print(f'=== start time: {time.strftime("%X")}')
    task1 = asyncio.create_task(factorial('fact-1', 3))  # ①
    task2 = asyncio.create_task(factorial('fact-2', 7))  # ②

    print('await start ...')  # ③

    r = await task2  # ④
    print(f'=== Second time:{time.strftime("%X")}, Result: <{r.name}, {r.result}>')

    r = await task1 # ⑤
    print(f'=== Third time:{time.strftime("%X")}, Result: <{r.name}, {r.result}>')

await main()

执行结果:

=== start time: 22:44:25
await start ...
> 22:44:28 - (fact-1) Factorial(3)=6
> 22:44:32 - (fact-2) Factorial(7)=5040
=== Second time:22:44:32, Result: <fact-2, 5040>
=== Third time:22:44:32, Result: <fact-1, 6>

程序三跟程序二不同点在于create_task()处没有添加await去阻塞。

create_task()会将协程封装成一个Task,并返回一个Task对象。该方法不会阻塞main()程序,所以,①②处的代码瞬间就执行完了,并继续执行下去。

create_task()创建的Task,会自动添加到事件循环上,通俗的说,事件循环就是调度Task并发的,但何时并发,我们是无法控制的。

所以,使用create_task()来并发是“隐性”的。这个隐性是指,我们没有类似多线程那样有很明确的start()启动并发。而是执行了create_task()后就算是启动了并发。

当程序执行到③时,①②创建的两个任务可能在并发执行了(如果没有,可能过一会儿就并发了)。

程序四的执行情况就说明了create_task()执行后在并发执行

现在疑惑,既然并发执行了,为什么需要在④和⑤上添加await?

用程序五来说明

# 程序四
async def main():
    """
    task是自行调度的,即使没有await task1也是在执行的。
    """
    print(f'\n=== start time: {time.strftime("%X")}')
    asyncio.create_task(factorial('fact-1', 3))
    asyncio.create_task(factorial('fact-2', 7))

    print('await start ...')
    await asyncio.sleep(20)
    print(f'=== End time: {time.strftime("%X")}')

await main()

执行结果:

=== start time: 22:44:43
await start ...
> 22:44:46 - (fact-1) Factorial(3)=6
> 22:44:50 - (fact-2) Factorial(7)=5040
=== End time: 22:45:03
# 程序五
async def main():
    """
    create_task()会返回一个task对象,task会被事件调度器自动处理,
    该方法不会阻塞,所以返回task对象后会执行执行。
    """
    print(f'=== start time: {time.strftime("%X")}')
    asyncio.create_task(factorial('fact-1', 3))  # ①
    asyncio.create_task(factorial('fact-2', 7))  # ②

    print('await start ...')
    await asyncio.sleep(6)  # ③
    print(f'=== End time: {time.strftime("%X")}')

await main()

执行结果:

=== start time: 22:45:03
await start ...
> 22:45:06 - (fact-1) Factorial(3)=6
=== End time: 22:45:09
> 22:45:10 - (fact-2) Factorial(7)=5040

①处是个耗时3s的操作,②是耗时7s的操作。③是用来模拟main()这个主线程执行完要6s。而当主线程执行完后就关闭了。

在主线程存活这6s内,开启的协程①是可以执行结束的,而协程②要7s才能执行完。故当主线程关闭了,里面还没有执行完的任务(这里是②)也一同被清了。类似电脑主机关机了,里面还在跑的程序也被强制关停了。

所以,在并发上,await是可以用来保证并发任务执行结束,同时接收并发任务返回值的,如程序三。

如果不关心是否要执行完,确实是可以不用await的。比如在一个死循环里或者不停服的代码里并发。

所以,await放在程序的位置是很关键的。如果要并发多个任务,写完多个create_task(),如果后面的代码跟并发返回结果无关联,最好将await放在其他代码后面。不然直接create_task()就await了,导致后面的代码无法执行,这样效果就不甚理想。

async main():
    ...
    task1 = asyncio.create_task(coro())   # ①
    task2 = asyncio.create_task(coro())   # ②
    ...
    ...   # 这里表示其他要执行的逻辑代码  ③
    r = await task1   # await不直接放在②后面,而放在③后面,这样③处的执行才不被阻塞。
    r2 = await task2

同理,在使用asyncio.as_completed()来开启并发,如何使用await,是跟使用create_task()思路一样的。

如果不需要返回值并且不关心是否正常执行完,可以不使用await,如果要用且在不影响其他逻辑代码情况,尽量对任务并发的结果放在后面处理。

但如果使用asyncio.gather()asyncio.wait()来并发操作,因为这两个方法语法原因,都是需要使用到await的。

所以要用asyncio.gather()asyncio.wait(),不用想,在它们前面加await就好。

  • 10
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值