Bootstrap

python异步任务协程yield greelet gevent asyncio async await高级使用

目录

一、什么是协程

二、协程实现方式

1. yield实现

2. greenlet实现

3. gevent实现

4. gevent-monkey补丁 

5. asyncio装饰器

6. async&await关键字

三、 asyncio使用

1. 相关概念

(1)event_loop(事件循环)

(2)coroutine function(协程函数)

(3)coroutine object(协程对象)

(3)Task(任务)

(4)Future

(5)async

(6)await

2. 运行一个协程对象

(1)方式1

(2)方式2

(3)方式3

(4)方式4

3. 协程并发执行

(1)asyncio.run()

(2)loop.run_until_complete

4. 并发等待

(1)wait

(2)gather

(3)wait_for

(4)as_completed

5. 获取协程返回值

(1)通过task.result()

 (2)通过add_done_callback()回调

6. 协程并发常用方式

(1)event_loop&asyncio.wait | asyncio.gather

(2)asyncio.run&asyncio.wait | asyncio.gather

(3)asyncio.run&asyncio.as_complete


一、什么是协程

协程(Coroutine),也可以被称为微线程,也称为用户级线程,是一种用户态内的上下文切换技术。在不开辟新线程的基础上完成多任务,其实就是通过一个线程实现代码间相互切换执行,在一个线程(协程)中,遇到io等待时间,线程可以利用这个等待时间去做其他事情。

并发的本质:切换+保存状态

cpu正在运行一个任务,会在两种情况下切走去执行其他的任务(切换由操作系统强制控制),一种情况是该任务发生了阻塞,另外一种情况是该任务计算的时间过长或有一个优先级更高的程序替代了它。

在Python中有多种方式可以实现协程,如下:

  • yield:生成器,借助生成器的特点可以实现协程代码;

  • greenlet:是一个第三方模块,用于实现协程代码,需要手动切换

  • gevent:基于greenlet实现的,遇到IO操作能够自动切换任务,等待IO操作完成,再在适当的时候切换回来继续执行;

  • asyncio:在Python3.4中引入的模块用于编写协程代码;

  • async & awiat:在Python3.5中引入的两个关键字,结合asyncio模块可以更方便的编写协程代码。

二、协程实现方式

1. yield实现

#!/usr/bin/python3
# -*- coding: utf-8 -*-
import time


# yield 实现协程
# 协程之间执行任务按照一定顺序交替执行
def task1():
    for i in range(5):
        print("---task1---", i)
        time.sleep(0.5)
        yield


def task2():
    for i in range(5):
        print("---task2---", i)
        time.sleep(0.5)
        yield


def main():
    t1 = task1()
    t2 = task2()
    while True:
        try:
            next(t1)
            next(t2)
        except StopIteration:
            break
    print("main finsh...")


if __name__ == '__main__':
    main()

运行结果:

---task1--- 0
---task2--- 0
---task1--- 1
---task2--- 1
---task1--- 2
---task2--- 2
---task1--- 3
---task2--- 3
---task1--- 4
---task2--- 4
main finsh... 

2. greenlet实现

#!/usr/bin/python3
# -*- coding: utf-8 -*-
import time

from greenlet import greenlet


def task1(num=5):
    for i in range(num):
        print("tak1----", i)
        # time.sleep(1)
        g2.switch()


def task2(num=5):
    for i in range(num):
        print("tak2----", i)
        time.sleep(1)
        g1.switch()


g1 = greenlet(task1)
g2 = greenlet(task2)
g1.switch()

输出结果:

tak1---- 0
tak2---- 0
tak1---- 1
tak2---- 1
tak1---- 2
tak2---- 2
tak1---- 3
tak2---- 3
tak1---- 4
tak2---- 4

3. gevent实现

#!/usr/bin/python3
# -*- coding: utf-8 -*-
import gevent
import time


def task(name, num):
    for i in range(num):
        print(gevent.getcurrent(), name, i)
        # 模拟一个耗时操作,注意不能使用time模块的sleep
        # time.sleep(1) # 顺序执行
        gevent.sleep(1) # 模拟耗时,遇到io操作切换到其他协程执行


def main():
    # spawn 方式创建协程
    g1 = gevent.spawn(task, 'task1...', 9)
    g2 = gevent.spawn(task, 'task2...', 5)
    g3 = gevent.spawn(task, 'task3...', 3)

    # d等待协程运行
    # g1.join()
    # g2.join()
    # g3.join()
    gevent.joinall([
        g1, g2, g3
    ])


if __name__ == '__main__':
    main()

运行结果如下:

<Greenlet at 0x19a1419c900: task('task1...', 9)> task1... 0
<Greenlet at 0x19a14338e00: task('task2...', 5)> task2... 0
<Greenlet at 0x19a1433d120: task('task3...', 3)> task3... 0
<Greenlet at 0x19a1419c900: task('task1...', 9)> task1... 1
<Greenlet at 0x19a14338e00: task('task2...', 5)> task2... 1
<Greenlet at 0x19a1433d120: task('task3...', 3)> task3... 1
<Greenlet at 0x19a1419c900: task('task1...', 9)> task1... 2
<Greenlet at 0x19a14338e00: task('task2...', 5)> task2... 2
<Greenlet at 0x19a1433d120: task('task3...', 3)> task3... 2
<Greenlet at 0x19a1419c900: task('task1...', 9)> task1... 3
<Greenlet at 0x19a14338e00: task('task2...', 5)> task2... 3
<Greenlet at 0x19a1419c900: task('task1...', 9)> task1... 4
<Greenlet at 0x19a14338e00: task('task2...', 5)> task2... 4
<Greenlet at 0x19a1419c900: task('task1...', 9)> task1... 5
<Greenlet at 0x19a1419c900: task('task1...', 9)> task1... 6
<Greenlet at 0x19a1419c900: task('task1...', 9)> task1... 7
<Greenlet at 0x19a1419c900: task('task1...', 9)> task1... 8

4. gevent-monkey补丁 

#!/usr/bin/python3
# -*- coding: utf-8 -*-
import gevent
import time
from gevent import monkey


monkey.patch_all()

def task(name, num):
    for i in range(num):
        print(gevent.getcurrent(), name, i)
        # 模拟一个耗时操作,注意不能使用time模块的sleep
        time.sleep(1) # 顺序执行


def main():
    gevent.joinall([
        gevent.spawn(task, 'task1...', 5),
        gevent.spawn(task, 'task2...', 5),
        gevent.spawn(task, 'task3...', 3)
    ])
    print("main finish...")


if __name__ == '__main__':
    main()

 运行结果:

<Greenlet at 0x22011992020: task('task1...', 5)> task1... 0
<Greenlet at 0x220119fd800: task('task2...', 5)> task2... 0
<Greenlet at 0x220119fd8a0: task('task3...', 3)> task3... 0
<Greenlet at 0x22011992020: task('task1...', 5)> task1... 1
<Greenlet at 0x220119fd800: task('task2...', 5)> task2... 1
<Greenlet at 0x220119fd8a0: task('task3...', 3)> task3... 1
<Greenlet at 0x22011992020: task('task1...', 5)> task1... 2
<Greenlet at 0x220119fd800: task('task2...', 5)> task2... 2
<Greenlet at 0x220119fd8a0: task('task3...', 3)> task3... 2
<Greenlet at 0x22011992020: task('task1...', 5)> task1... 3
<Greenlet at 0x220119fd800: task('task2...', 5)> task2... 3
<Greenlet at 0x22011992020: task('task1...', 5)> task1... 4
<Greenlet at 0x220119fd800: task('task2...', 5)> task2... 4
main finish...

5. asyncio装饰器

import asyncio

@asyncio.coroutine
def task1():
    print(1)
    yield from asyncio.sleep(2)  # 遇到IO耗时操作,自动化切换到tasks中的其他任务
    print(2)


@asyncio.coroutine
def task2():
    print(3)
    yield from asyncio.sleep(2) # 遇到IO耗时操作,自动化切换到tasks中的其他任务
    print(4)


tasks = [
    asyncio.ensure_future(task1()),
    asyncio.ensure_future(task2())
]

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

运行结果:

1

3

2

4

6. async&await关键字

import asyncio


async def task1():
    for i in range(5):
        print("---task1---", i)
        await asyncio.sleep(0.5)


async def task2():
    for i in range(5):
        print("---task1---", i)
        await asyncio.sleep(0.5)

def main():
    loop = asyncio.get_event_loop()
    tasks = [
        asyncio.ensure_future(task1()),
        asyncio.ensure_future(task2())
    ]

    loop.run_until_complete(asyncio.wait(tasks))


if __name__ == '__main__':
    main()

运行结果:

---task1--- 0
---task1--- 0
---task1--- 1
---task1--- 1
---task1--- 2
---task1--- 2
---task1--- 3
---task1--- 3
---task1--- 4
---task1--- 4 

三、 asyncio使用

在Python3.4之前官方未提供协程的类库,一般大家都是使用greenlet、gevevnt等来实现。

在Python3.4发布后官方正式支持协程,asyncio模块。

在Python3.5版本中正式引入async & awit 关键字,基于他编写的协程代码可以更加简洁。

在Python3.8之后 @asyncio.coroutine 装饰器就被移除,推荐使用async & awit 关键字实现协程代码。

asyncio 是用来编写并发代码的库,被用作多个提供高性能 Python 异步框架的基础,包括网络和网站服务,数据库连接库,分布式任务队列等等。

asyncio 往往是构建 IO 密集型和高层级结构化网络代码的最佳选择。

1. 相关概念

(1)event_loop(事件循环)

管理所有的事件,可以理解是一个无限循环,我们可以把一些函数注册到这个事件循环上,当满足条件时,就会调用对应的处理方法。

(2)coroutine function(协程函数)

协程函数,是一个使用async关键字定义的函数,他的调用不会立即执行函数,会返回一个协程对象。

(3)coroutine object(协程对象)

调用协程函数返回的对象,协程对象需要注册到事件循环中,由事件循环调用执行。

(3)Task(任务)

事件循环调度的最小单元,把协程对象打包成一个Task,这个过程会直接注册这个协程进入事件循环中。

是Future的子类,一个协程对象就是一个原生可以挂起的函数,任务则是对协程对象的进一步封装,其中包含任务的各种状态。
使用高层级的 asyncio.create_task() 函数来创建 Task 对象,也可用低层级的 loop.create_task() 或 ensure_future() 函数。不建议手动实例化 Task 对象。
常用方法:

  • result():返回 Task 的结果,有返回值就返回,无返回值为None。
  • done():如果 Task 对象 已完成 则返回 True。
  • cancelled():如果 Task 对象 被取消 则返回 True。

(4)Future

代表将来执行、尚未完成的计算或结果。

Task的父类,一个比较底层的对象,用于支持底层回调式代码与高层异步/等待式代码交互,但是一般不会使用Future对象,如果要使用也是在Task对象使用。

常用方法:

  • result()
  • set_result()
  • done()
  • cancelled()
  • cancel()

(5)async

python3.5用于定义协程的关键字,定义函数时加上async修饰就是定义一个协程函数,即async def func(),则该函数为协程函数,协程函数返回的对象即为协程对象。

(6)await

用于挂起阻塞的异步调用,await后面是一个可等待对象(Future、协程、Task),用于告诉even loop在此协程中需要等待后面的函数运行完成后才能继续,运行完成后返回结果。

2. 运行一个协程对象

要运行一个协程对象,必须把他注册到事件循环里,那么有三种方法:

  1. 直接把协程对象注册到事件循环
  2. 在别的协程中await它,间接的注册到事件循环中
  3. 把它包装成一个Task对象,注册入事件循环中
  4. python3.7 以后的版本直接使用asyncio.run即可。此函数总是会创建一个新的事件循环并在结束时关闭。它应当被用作 asyncio 程序的主入口点,理想情况下应当只被调用一次。

(1)方式1

#!/usr/bin/python3
# -*- coding: utf-8 -*-
import asyncio
import time


# async 修饰,定义了一个协程函数
async def task(name, num):
    for i in range(num):
        print("---task---", name, i)
        await asyncio.sleep(1) # asyncio.sleep() 是异步操作,前面 await 意思是将这个 sleep() 任务挂起

    return name


def main():
    # 获取事件循环
    loop = asyncio.get_event_loop()

    # 创建协程对象
    t1 = task('A', 5) # <class 'coroutine'>
    print(type(t1))


    # 将协程对象或者task添加到事件循环并执行
    # run_until_complete参数为协程对象或者一个task对象,
    # 返回他们执行完的return结果(无返回值则为None)
    res = loop.run_until_complete(t1)
    print(res)


if __name__ == '__main__':
    main()

运行结果:

<class 'coroutine'>
---task--- A 0
---task--- A 1
---task--- A 2
---task--- A 3
---task--- A 4
A

(2)方式2

#!/usr/bin/python3
# -*- coding: utf-8 -*-

import asyncio
import time


# async 修饰,定义了一个协程函数
async def task(name, num):
    for i in range(num):
        print("---task---", name, i)
        await asyncio.sleep(0.5) # asyncio.sleep() 是异步操作,前面 await 意思是将这个 sleep() 任务挂起

    return name

# 在别的协程中await它,间接的注册到事件循环里
async def run():
    await task("aaa",5)


def main():
    # 获取事件循环
    loop = asyncio.get_event_loop()
    loop.run_until_complete(run())


if __name__ == '__main__':
    main()

运行结果:

---task--- aaa 0
---task--- aaa 1
---task--- aaa 2
---task--- aaa 3
---task--- aaa 4

(3)方式3

#!/usr/bin/python3
# -*- coding: utf-8 -*-
"""
@Author   : charleiy
@DateTime : 2024-07-04 11:30:44
@Desc     : None
"""
import asyncio
import time


# async 修饰,定义了一个协程函数
async def task(name, num):
    for i in range(num):
        print("---task---", name, i)
        await asyncio.sleep(0.5) # asyncio.sleep() 是异步操作,前面 await 意思是将这个 sleep() 任务挂起

    return name


def main():
    # 获取事件循环
    loop = asyncio.get_event_loop()

    # 创建task 方式1
    t1 = loop.create_task(task('a',5)) # <class '_asyncio.Task'>
    print(type(t1))

    # 创建task 方式2
    # t1 = asyncio.ensure_future(task('Aa',5)) # <class '_asyncio.Task'>
    # print(type(t1))

    # 将协程对象或者Task添加到事件循环并执行,
    # run_until_complete参数为协程对象或者一个task对象, 返回他们执行完的return结果(无返回值则为None)
    # 如果传入的是task对象,可以调用 task.result()获取返回结果
    res = loop.run_until_complete(t1)
    print(res)


if __name__ == '__main__':
    main()

运行结果:

 <class '_asyncio.Task'>
---task--- a 0
---task--- a 1
---task--- a 2
---task--- a 3
---task--- a 4
a

(4)方式4

#!/usr/bin/python3
# -*- coding: utf-8 -*-
"""
@Author   : charleiy
@DateTime : 2024-07-04 11:33:44
@Desc
"""

import asyncio
import time


# async 修饰,定义了一个协程函数
async def task(delay: int, msg):
    await asyncio.sleep(delay)
    print(msg)


async def main():
    await task(1,"hello")
    await task(2,"word")


if __name__ == '__main__':
    print(f"start at {time.strftime('%X')}")
    asyncio.run(main())
    print(f"end at {time.strftime('%X')}")

运行结果:

start at 22:21:39
hello
word
end at 22:21:42

可以发现,整个过程执行时间是3秒, 为什么不是2秒?

代码执行分析

asyncio.run(main()) 把main返回的协程对象放到了event loop,转为了协程任务,event loop发现当前有一个可执行任务,开始执行;执行到 await async_test(1,“hello”) 时发现有await,需要等待该协程对象,执行完之后再执行 await async_test(2,“world”),所以耗时3秒。

3. 协程并发执行

从上面执行结果上来看没有意义,因为并没有并发执行,那么如何并发呢?

使用方式3:把协程对象包装成一个Task对象,注册入事件循环中即可。

(1)asyncio.run()

#!/usr/bin/python3
# -*- coding: utf-8 -*-
"""
@Author   : charleiy
@DateTime : 2024-07-04 11:40:44
@Desc
"""

import asyncio
import time


# async 修饰,定义了一个协程函数
async def task(delay: int, msg):
    await asyncio.sleep(delay)
    print(msg)


async def main():
    # task对象创建
    # 高层级的 asyncio.create_task() 函数来创建 Task 对象,
    # 也可用低层级的 asyncio.ensure_future() 或 loop.create_task() 函数创建Task对象
    # 不建议手动实例化 Task 对象。

    # 1. asyncio.create_task()
    # t1 = asyncio.create_task(task(1,"hello"))
    # t2 = asyncio.create_task(task(2,"word"))

    # 2. asyncio.ensure_future()
    # t1 = asyncio.ensure_future(task(1,"hello"))
    # t2 = asyncio.ensure_future(task(2,"word"))

    # 3. loop.create_task()
    loop = asyncio.get_event_loop()
    t1 = loop.create_task(task(1,"hello"))
    t2 = loop.create_task(task(2,"world"))

    await t1
    await t2 # 这里可以不执行 await t1 因为t2执行时间长于1秒,t2执行完t1肯定执行完了

if __name__ == '__main__':
    print(f"start at {time.strftime('%X')}")
    asyncio.run(main())
    print(f"end at {time.strftime('%X')}")

运行结果:

start at 22:32:48
hello
word
end at 22:32:50 

可以发现执行结果2秒,并行执行了

(2)loop.run_until_complete

#!/usr/bin/python3
# -*- coding: utf-8 -*-
"""
@Author   : charleiy
@DateTime : 2024-07-04 11:40:44
@Desc
"""

import asyncio
import time


# async 修饰,定义了一个协程函数
async def task(delay: int, msg):
    await asyncio.sleep(delay)
    print(msg)


def main():
    loop = asyncio.get_event_loop()

    # task对象创建
    # t1 = asyncio.ensure_future(task(1,"hello"))
    # t2 = asyncio.ensure_future(task(2,"word"))

    t1 = loop.create_task(task(1,"hello"))
    t2 = loop.create_task(task(2,"word"))

    loop.run_until_complete(asyncio.wait([t1,t2]))

if __name__ == '__main__':
    print(f"start at {time.strftime('%X')}")
    main()
    print(f"end at {time.strftime('%X')}")

运行结果:

start at 09:47:57
hello
word
end at 09:47:59

4. 并发等待

如3.1中我们创建100个协程任务,总不能一行一行的写100个 await 吧?接下来就看看如何并发等待100个任务

(1)wait

async def wait(fs, *, timeout=None, return_when=ALL_COMPLETED)
同时运行fs中的等待对象并阻塞 ,直到return_when指定的条件; 不允许传入协程对象,可以传入task对象、future对象。

  • 返回二元组 (tasks/futures):(done,pending)
  • 用法:done, pending = await asyncio.wait(fs)
  • return_when 指定此函数应在何时返回。它必须为以下常数之一:
    • FIRST_COMPLETED 函数将在任意可等待对象结束或取消时返回。
    • FIRST_EXCEPTION 函数将在任意可等待对象因引发异常而结束时返回。当没有引发任何异常时它就相当于 ALL_COMPLETED。
    • ALL_COMPLETED 函数将在所有可等待对象结束或取消时返回。
      与 wait_for() 不同,wait() 在超时发生时不会取消可等待对象
#!/usr/bin/python3
# -*- coding: utf-8 -*-
"""
@Author   : charleiy
@DateTime : 2024-07-04 22:10:24
@Desc None
"""

import asyncio
import time


# async 修饰,定义了一个协程函数
async def task(delay: int, msg):
    await asyncio.sleep(delay)
    print(asyncio.current_task().get_name(), msg)

    return msg


async def main():
    # task对象创建
    tasks = list()
    for i in range(5):
        tasks.append(asyncio.create_task(task(2, f"hello-{i}")))
        # tasks.append(task(2, f"hello-{i}")) # TypeError: Passing coroutines is forbidden, use tasks explicitly.

    # 并发等待 asyncio.wait 不可以传入协程对象,可以传入task对象、future对象
    done, pending = await asyncio.wait(tasks)

    # 对应协程执行完毕,获取返回值
    for t in done:
        print(f"协程{t.get_name()}返回结果:{t.result()}")


if __name__ == '__main__':
    print(f"start at {time.strftime('%X')}")
    asyncio.run(main())
    print(f"end at {time.strftime('%X')}")

运行结果:
start at 23:54:07
Task-2 hello-0
Task-4 hello-2
Task-6 hello-4
Task-3 hello-1
Task-5 hello-3
协程Task-4返回结果:hello-2
协程Task-6返回结果:hello-4
协程Task-5返回结果:hello-3
协程Task-2返回结果:hello-0
协程Task-3返回结果:hello-1
end at 23:54:09

(2)gather

def gather(*coros_or_futures, return_exceptions=False)

并发的运行协程或者task,并把他们的结果按照顺序方法列表中返回,协程并不一定按顺序执行,但结果是按顺序返回。

  • 接收的是列表(协程、task、future对象
  • 返回结果将是一个由所有返回值聚合而成的列表,与 coros_or_futures中可等待对象的顺序一致。
  • 如果 return_exceptions 为 False (默认),所引发的首个异常会立即传播给等待 gather() 的任务。coros_or_futures序列中的其他可等待对象 不会被取消 并将继续运行。
  • 如果return_exceptions是True,异常的处理方式一样成功的结果,并在结果列表汇总
  • 如果gather()被取消,所有提交的awaitables(尚未完成)也被取消。
#!/usr/bin/python3
# -*- coding: utf-8 -*-
"""
@Author   : charleiy
@DateTime : 2024-07-04 22:21:10
@Desc None
"""


import asyncio
import time


# async 修饰,定义了一个协程函数
async def task(delay: int, msg):
    await asyncio.sleep(delay)
    print(asyncio.current_task().get_name(), msg)

    return msg


async def main():
    # task对象创建
    tasks = list()
    for i in range(5):
        # tasks.append(asyncio.create_task(task(2, f"hello-{i}")))
        # tasks.append(asyncio.ensure_future(task(2, f"hello-{i}")))
        tasks.append(task(2, f"hello-{i}"))

    # 并发等待 asyncio.gather 可以传入协程对象、task对象、future对象
    res = await asyncio.gather(*tasks)

    # 对应协程执行完毕,获取返回值, 返回值顺序与传入的一致
    for t in res:
        print("返回结果:", t)


if __name__ == '__main__':
    print(f"start at {time.strftime('%X')}")
    asyncio.run(main())
    print(f"end at {time.strftime('%X')}")

运行结果:

start at 23:50:57
Task-2 hello-0
Task-4 hello-2
Task-6 hello-4
Task-3 hello-1
Task-5 hello-3
返回结果: hello-0
返回结果: hello-1
返回结果: hello-2
返回结果: hello-3
返回结果: hello-4
end at 23:50:59

(3)wait_for

async def wait_for(fut, timeout):

超时会取消可等待对象,会抛出异常asyncio.TimeoutError

  • fut:参数只接收一个fut(协程、task、future)
  • timeout:可以为 None,也可以为 float 或 int 型数值表示的等待秒数。如果 timeout 为 None,则等待直到完成
#!/usr/bin/python3
# -*- coding: utf-8 -*-
"""
@Author   : charleiy
@DateTime : 2024-07-04 22:27:10
@Desc None
"""
import asyncio
import time


async def task(delay:int, msg):
    await asyncio.sleep(delay)
    print(msg)


async def main():
    try:
        await asyncio.wait_for(task(2, "hello"), timeout=1)
    except asyncio.TimeoutError:
        print("任务超时...")


if __name__ == '__main__':
    print(f"start at {time.strftime('%X')}")
    asyncio.run(main())
    print(f"end at {time.strftime('%X')}")

运行结果:

start at 00:12:14
任务超时...
end at 00:12:15

(4)as_completed

def as_completed(fs, *, timeout=None):
#Return an iterator whose values are coroutines.

并发地运行 fs 集合中的 可等待对象。返回一个 Future 对象的迭代器。返回的每个 Future 对象代表来自剩余可等待对象集合的最早结果。

#!/usr/bin/python3
# -*- coding: utf-8 -*-
"""
@Author   : charleiy
@DateTime : 2024-07-04 23:27:10
@Desc None
"""
import asyncio
import time


async def task1(delay: int,msg):
    await asyncio.sleep(delay)
    print(msg)
    return msg


async def main():
    tasks = list()
    for i in range(5):
        tasks.append(asyncio.ensure_future(task1(2, "hello")))
        # tasks.append(task1(1, "hello"))

        for task in asyncio.as_completed(tasks):
            res = await task
            print("返回结果:", res)


if __name__ == '__main__':
    print(f"start at {time.strftime('%X')}")
    asyncio.run(main())
    print(f"end at {time.strftime('%X')}")

5. 获取协程返回值

(1)通过task.result()

可通过调用 task.result() 方法来获取协程的返回值,但是只有运行完毕后才能获取,若没有运行完毕,result()方法不会阻塞去等待结果,而是抛出 asyncio.InvalidStateError 错误

#!/usr/bin/python3
# -*- coding: utf-8 -*-
"""
@Author   : charleiy
@DateTime : 2024-07-04 11:30:44
@Desc     : None
"""
import asyncio
import time


# async 修饰,定义了一个协程函数
async def task(name, num):
    for i in range(num):
        print("---task---", name, i)
        await asyncio.sleep(0.5) # asyncio.sleep() 是异步操作,前面 await 意思是将这个 sleep() 任务挂起
    return name * num


def main():
    # 获取事件循环
    loop = asyncio.get_event_loop()


    tasks = list()
    for name in list('ABC'):
        # 创建task并加入到时间循环中
        t = loop.create_task(task(name, 5))
        tasks.append(t)
    # 将tasks加入到事件循环中并执行
    done,pending = loop.run_until_complete(asyncio.wait(tasks))
    # res = loop.run_until_complete(asyncio.gather(*tasks))

    # 获取返回值
    # 方式一 task.result()
    for t in tasks:
        print(t.result())
    
    # asyncio.await使用该方式获取
    # for i in done:
    #     print(i.result())

    # asyncio.gather 使用该方式获取
    # for i in res:
    #     print(i)


if __name__ == '__main__':
    main()

运行结果:

---task--- A 0
---task--- B 0
---task--- C 0
---task--- A 1
---task--- B 1
---task--- C 1
---task--- A 2
---task--- B 2
---task--- C 2
---task--- A 3
---task--- B 3
---task--- C 3
---task--- A 4
---task--- B 4
---task--- C 4
AAAAA
BBBBB
CCCCC

 (2)通过add_done_callback()回调

#!/usr/bin/python3
# -*- coding: utf-8 -*-
"""
@Author   : charleiy
@DateTime : 2024-07-05 11:30:44
@Desc     : None
"""
import asyncio
import time


# async 修饰,定义了一个协程函数
async def task(name, num):
    for i in range(num):
        print("---task---", name, i)
        await asyncio.sleep(0.5) # asyncio.sleep() 是异步操作,前面 await 意思是将这个 sleep() 任务挂起
    return name * num


def call_back(future):
    print("call back执行获取返回值:", future.result())


def main():
    # 获取事件循环
    loop = asyncio.get_event_loop()


    tasks = list()
    for name in list('ABC'):
        # 创建task并加入到时间循环中
        t = loop.create_task(task(name, 5))
        tasks.append(t)
        # 添加回回调获取返回值
        t.add_done_callback(call_back)
    # 将tasks加入到事件循环中并执行
    loop.run_until_complete(asyncio.wait(tasks))

    # 获取返回值
    # 方式一 task.result()
    # for t in tasks:
    #     print(t.result())


if __name__ == '__main__':
    main()

返回结果:

---task--- A 0
---task--- B 0
---task--- C 0
---task--- A 1
---task--- B 1
---task--- C 1
---task--- A 2
---task--- B 2
---task--- C 2
---task--- A 3
---task--- B 3
---task--- C 3
---task--- A 4
---task--- B 4
---task--- C 4
call back执行获取返回值: AAAAA
call back执行获取返回值: BBBBB
call back执行获取返回值: CCCCC

6. 协程并发常用方式

(1)event_loop&asyncio.wait | asyncio.gather

使用事件循环和asyncio.wait、asyncio.gather实现并发运行任务

#!/usr/bin/python3
# -*- coding: utf-8 -*-
"""
@Author   : charleiy
@DateTime : 2024-07-05 00:30:44
@Desc     : None
"""
import asyncio
import time


# async 修饰,定义了一个协程函数
async def task1(name, num):
    for i in range(num):
        print("---task---", name, i)
        await asyncio.sleep(1) # asyncio.sleep() 是异步操作,前面 await 意思是将这个 sleep() 任务挂起
    return name*num


def main():
    # 获取事件循环
    loop = asyncio.get_event_loop()
    
    # 创建 task 方式一
    tasks = [
        asyncio.ensure_future(task1('A',5)),
        asyncio.ensure_future(task1('B',5)),
        asyncio.ensure_future(task1('C',5)),
    ]
    # 创建 task 方式二
    # tasks = [
    #     loop.create_task(task1('aa',5)),
    #     loop.create_task(task1('bb',5)),
    #     loop.create_task(task1('cc',5)),
    # ]


    # 将tasks加入到事件循环中并执行
    # 方式一 使用 asyncio.wait
    done, pending = loop.run_until_complete(asyncio.wait(tasks))
    # loop.run_until_complete(asyncio.wait(tasks))的作用相当于:await asyncio.wait(tasks)

    for task in done:
        print(task.result())

    # 方式二 shiyong
    # res = loop.run_until_complete(asyncio.gather(*tasks))
    # for task in res:
    #     print(task)


if __name__ == '__main__':
    print(f"start at {time.strftime('%X')}")
    # asyncio.run(main())
    main()
    print(f"end at {time.strftime('%X')}")

运行结果:

start at 01:31:32
---task--- A 0
---task--- B 0
---task--- C 0
---task--- A 1
---task--- B 1
---task--- C 1
---task--- A 2
---task--- B 2
---task--- C 2
---task--- A 3
---task--- B 3
---task--- C 3
---task--- A 4
---task--- B 4
---task--- C 4
AAAAA
BBBBB
CCCCC
end at 01:31:37

(2)asyncio.run&asyncio.wait | asyncio.gather

使用asyncio.run和asyncio.wait、asyncio.gather实现并发运行任务

#!/usr/bin/python3
# -*- coding: utf-8 -*-
"""
@Author   : charleiy
@DateTime : 2024-07-05 00:30:44
@Desc     : None
"""
import asyncio
import time


# async 修饰,定义了一个协程函数
async def task1(name, num):
    for i in range(num):
        print("---task---", name, i)
        await asyncio.sleep(1) # asyncio.sleep() 是异步操作,前面 await 意思是将这个 sleep() 任务挂起
    return name*num


async def main():
    # 获取事件循环
    loop = asyncio.get_event_loop()

    # 创建 task 方式一
    tasks = [
        asyncio.ensure_future(task1('A',5)),
        asyncio.ensure_future(task1('B',5)),
        asyncio.ensure_future(task1('C',5)),
    ]
    # 创建 task 方式二
    # tasks = [
    #     loop.create_task(task1('aa',5)),
    #     loop.create_task(task1('bb',5)),
    #     loop.create_task(task1('cc',5)),
    # ]


    # 将tasks加入到事件循环中并执行
    # 方式一 使用 asyncio.wait
    # await asyncio.wait(tasks)

    # 方式二 shiyong
    await asyncio.gather(*tasks)

    for t in tasks:
        print(t.result())


if __name__ == '__main__':
    print(f"start at {time.strftime('%X')}")
    asyncio.run(main())
    # main()
    print(f"end at {time.strftime('%X')}")

(3)asyncio.run&asyncio.as_complete

使用asyncio.run和asyncio.as_complete实现并发运行任务

#!/usr/bin/python3
# -*- coding: utf-8 -*-
"""
@Author   : charleiy
@DateTime : 2024-07-05 00:30:44
@Desc     : None
"""
import asyncio
import time


# async 修饰,定义了一个协程函数
async def task1(name, num):
    for i in range(num):
        print("---task---", name, i)
        await asyncio.sleep(1) # asyncio.sleep() 是异步操作,前面 await 意思是将这个 sleep() 任务挂起
    return name*num


async def main():
    # 获取事件循环
    loop = asyncio.get_event_loop()

    # 创建 task 方式一
    tasks = [
        asyncio.ensure_future(task1('A',5)),
        asyncio.ensure_future(task1('B',5)),
        asyncio.ensure_future(task1('C',5)),
    ]
    # 创建 task 方式二
    # tasks = [
    #     loop.create_task(task1('aa',5)),
    #     loop.create_task(task1('bb',5)),
    #     loop.create_task(task1('cc',5)),
    # ]

    for t in asyncio.as_completed(tasks):
        res = await t
        print(res)


if __name__ == '__main__':
    print(f"start at {time.strftime('%X')}")
    asyncio.run(main())
    # main()
    print(f"end at {time.strftime('%X')}")

参考文档:

Coroutines and Tasks — Python 3.11.8 documentation

python-协程(async、await关键字与asyncio)_python async-CSDN博客

Python asyncio的基本使用_python中asyncio 用法-CSDN博客

Python多任务—协程(asyncio详解) 一_asyncio.wait-CSDN博客

python多任务—协程(asyncio详解) 二_python asyncio alltask cancel-CSDN博客

;