sxfxtf 发表于 2022-8-31 13:14

python之asyncio协程的回调函数返回值如何获取

import asyncio
import random

async def rnd(sleep_time):
    await asyncio.sleep(sleep_time)
    ret=random.randint(1,6)
    print("from rnd的结果是",ret,type(ret))
    return ret


def ret(x):
    result = x.result()
    print("收到参数为>>>>",result,type(result))
    return result+10

async def main():
    a1=obj.create_task(rnd(1))
    a2=asyncio.ensure_future(rnd(2))
    #问题a1 a2的返回值传入给ret函数,那么ret函数如果有返回值该怎么获取
    b1=a1.add_done_callback(ret)
    b2=a2.add_done_callback(ret)
    done,pending =await asyncio.wait()
    print(b1,b2)# 输出结果为 None None


obj=asyncio.get_event_loop()
obj.run_until_complete(main())
print("over")

想了解下如果回调函数也有返回值,那么我该如何获取.上面的b1 b2就是我要接受回调函数的变量,但是输出却是None.请各位大神指点

grekevin 发表于 2022-8-31 13:26

print(a1.result(), a2.result())

sxfxtf 发表于 2022-8-31 13:56

grekevin 发表于 2022-8-31 13:26


那个不是回调函数的返回值.
我要ret这个函数的返回值

zsq718 发表于 2022-8-31 13:59

学习一下

QQending 发表于 2022-8-31 14:14

```
import asyncio

async def coroutine_example():
    await asyncio.sleep(1)
    return 'zhihu ID: Zarten'

coro = coroutine_example()

loop = asyncio.get_event_loop()
task = loop.create_task(coro)
print('运行情况:', task)
try:
    print('返回值:', task.result())
except asyncio.InvalidStateError:
    print('task状态未完成,捕获了 InvalidStateError 异常')

loop.run_until_complete(task)
print('再看下运行情况:', task)
print('返回值:', task.result())
loop.close()
```
可以参考一下这个,另外,建议了解一下`coroutine`和`task`的区别
我之前看b站高天的视频,大概记录了一些

## 视频链接

asyncio的理解与入门,搞不明白协程?看这个视频就够了。

- https://b23.tv/yxQCWwb

await机制详解。再来个硬核内容,把并行和依赖背后的原理全给你讲明白

- https://b23.tv/G0bvKRk

# asyncio的理解与入门

## coroutine和task

### coroutine

async def的函数是coroutine func。

async def的函数单纯被call,会变成coroutine object,但只是单纯被call,会报错。       

### coroutine的执行方式

coroutine object可以通过以下方式被正常运行

- 直接`await coroutine_obj`

- 先`create_task(coroutine_obj)`
- 将coroutine_object变成task
- 然后再`await task`

`await gather(*coroutine_objs)`

- 这里也可以传入单个coroutine object。

### coroutine和task的关系

`create_task`**显式**地将coroutine object变成task

`gather`**隐式**地将coroutine object变成task。

而直接`await`一个coroutine_object,则**不会产生**task,类似立即调用了生成器(同步执行),不会将控制权交还给event loop。

## 整个asyncio的执行方式

### event loop

另外`asyncio.run(coroutine_func)`其实是先创建了一个`event loop`,由`event loop`来控制task的执行。

- `asyncio.run(coroutine_func)`,就会产生一个task。

- event loop调度的时候最小单位是一个task。
- event loop无法直接执行一个coroutine_obj。

### 直接await

直接`await coroutine_obj`会导致`event loop`逐个发现task的时候,**直接同步执行这步代码**,不会将控制权交还给event loop。

```python
import time
import asyncio

async def nap(delay:int):
    await asyncio.sleep(delay)
    return f"after {delay:d} second nap finished"

async def main():
    start_time = time.time()
    ret = await nap(1)
    ret2 = await nap(2)
    print(ret)
    print(ret2)
    print(f"time cost: {time.time()-start_time:.1f}s")

asyncio.run(main())
```

run result

```
after 1 second nap finished
after 2 second nap finished
time cost: 3.0s
```

### create_task配合await

而通过create_task,再**逐个**await task,可以**异步执行**。

```python
import time
import asyncio

async def nap(delay:int):
    await asyncio.sleep(delay)
    return f"after {delay:d} second nap finished"

async def main():
    start_time = time.time()
    task = asyncio.create_task(nap(1))
    task2 = asyncio.create_task(nap(2))
    ret = await task
    ret2 = await task2
    print(ret)
    print(ret2)
    print(f"time cost: {time.time()-start_time:.1f}s")

asyncio.run(main())
```

run result

```
after 1 second nap finished
after 2 second nap finished
time cost: 2.0s
```

### gather

`await gather(coroutine_objs)`,也可以**异步执行**。

```python
import time
import asyncio

async def nap(delay:int):
    await asyncio.sleep(delay)
    return f"after {delay:d} second nap finished"

async def main():
    start_time = time.time()
    rets = await asyncio.gather(
      nap(1),
      nap(2),
    )
    ret, ret2 = rets
    print(ret)
    print(ret2)
    print(f"time cost: {time.time()-start_time:.1f}s")

asyncio.run(main())
```

run result

```
after 1 second nap finished
after 2 second nap finished
time cost: 2.0s       
```

### 总结

直接`await coutine_obj`,会导致这步代码**同步执行**。

「create_task配合await」和「gather」,可以**异步执行**,大致逻辑是。

- 会让`event loop`先收到外层的tasks。
- 在等待时,逐个发现是否还有其他task可执行。有点类似树结构,保证子节点们执行完,才能去执行父节点,实现异步执行。

### 补充asyncio.wait

相比于`gather`, `wait`可以

- 返回已完成的任务和待完成的任务,这两个序列。
- 而且传入对象必须为task的序列,不能为生成器,也不能为单个task。
- 不过如下方示例,单个task的序列是可以的。

```python
import time
import random
import asyncio

async def foo(num:int):

    return num

async def main():
    task = asyncio.create_task(foo(random.randint(0,100)))
    dones, pendings = await asyncio.wait() # 必须是一个task的序列,不能是单个task。

    # 这里说明dones,其实是完成的task
    if task in dones:
      result = task.result()
      print(result)
    print('='*30)
    tasks =
    dones, pendings = await asyncio.wait(tasks)
    for task in dones:
      print(task.result())

asyncio.run(main())
```

# await机制详解

await后面可以是coroutine object、task、future这三种。

```python
import asyncio

async def main():
    await asyncio.sleep(1)
   
dis.dis(main())
```

run result

```
98         0 LOAD_GLOBAL            0 (asyncio)
            2 LOAD_METHOD            1 (sleep)
            4 LOAD_CONST               1 (1)
            6 CALL_METHOD            1
            8 GET_AWAITABLE
             10 LOAD_CONST               0 (None)
             12 YIELD_FROM
             14 POP_TOP
             16 LOAD_CONST               0 (None)
             18 RETURN_VALUE
```

其中主要部分

```
            8 GET_AWAITABLE
             10 LOAD_CONST               0 (None)
             12 YIELD_FROM
```

async里,当return一个值的时候,其机制类似generator。

- return,其实等同`raise StopIteration`,return的值,则设置为`StopIteration`的`Exception`的value

```python
try:
    raise StopIteration('Value here')
except Exception as e:
    print(e) # Value here
```

QQending 发表于 2022-8-31 14:33

本帖最后由 QQending 于 2022-8-31 14:39 编辑

```
import asyncio
import random

async def rnd(sleep_time):
    await asyncio.sleep(sleep_time)
    ret=random.randint(1,6)
    print("from rnd的结果是",ret,type(ret))
    return ret


def ret(x):
    result = x.result()
    print("收到参数为>>>>",result,type(result))
    return result+10

async def main():
    task = asyncio.create_task(rnd(1))
    task2 =asyncio.create_task(rnd(2))
    #问题a1 a2的返回值传入给ret函数,那么ret函数如果有返回值该怎么获取
    dones,pendings =await asyncio.wait()
    for task in dones:
         print(task.result())# 输出结果为 6 1
    print('over!')

asyncio.run(main())
```
我改成high-level API版本以后,你看一下。
或者
```
import asyncio
import random
from asyncio import ALL_COMPLETED

async def rnd(sleep_time):
    await asyncio.sleep(sleep_time)
    ret=random.randint(1,6)
    print("from rnd的结果是",ret,type(ret))
    return ret


def ret(x):
    result = x.result()
    print("收到参数为>>>>",result,type(result))
    return result+10

async def main():
    task = asyncio.create_task(rnd(1))
    future =asyncio.ensure_future(rnd(2))
    dones,pendings =await asyncio.wait(, return_when=ALL_COMPLETED)
    for task in dones:
         print(task.result())# 输出结果为 2 3
    print('over!')

asyncio.run(main())
```

sxfxtf 发表于 2022-8-31 14:58

QQending 发表于 2022-8-31 14:33
```
import asyncio
import random


感谢热心解答.不过可能我的问题没描述清楚.
我有一个协程函数rnd和一个普通函数ret. 用add_done_callback让ret接收rnd的返回值,然后加10以后再进行返回.
rnd的协程函数的返回值是 1-6
ret函数的返回值是11-16
我想获取的是ret的返回值,所以只是从返回的数字就看得出上面只是获取了rnd协程函数的返回值.但是并没有我要的ret普通函数的返回值

我爱猫哥 发表于 2022-8-31 15:54

参考下这个代码
import time
import asyncio

async def run(url):
    print("开始向'%s'要数据……"%(url))
    # 向百度要数据,网络IO
    await asyncio.sleep(5)
    data = "'%s'的数据"%(url)
    print("给你数据")
    return data

# 定义一个回调函数
def call_back(future):
    print("call_back:", future.result())

coroutine = run("百度")
# 创建一个任务对象
task = asyncio.ensure_future(coroutine)

# 给任务添加回调,在任务结束后调用回调函数
task.add_done_callback(call_back)

loop = asyncio.get_event_loop()
loop.run_until_complete(task)

sxfxtf 发表于 2022-8-31 16:09

我爱猫哥 发表于 2022-8-31 15:54
参考下这个代码
import time
import asyncio


这个代码其实和我写的一样都是利用add_done_callback在协程函数运行完毕后传值给call_back函数.运行print是没问题.但是如果把你的call_back最后return一个值的话.就获取不到.

grekevin 发表于 2022-8-31 17:44

这样写执行效果是一样的
import asyncio
import random


async def rnd(sleep_time):
    await asyncio.sleep(sleep_time)
    ret = random.randint(1, 6)
    print("from rnd的结果是", ret, type(ret))
    return get_reuslt(ret)


def get_reuslt(x):
    print("收到参数为>>>>", x, type(x))
    return x + 10
   


async def main():
    a1 = loop.create_task(rnd(1))
    a2 = asyncio.ensure_future(rnd(2))
    done, pending = await asyncio.wait()
    print(a1.result(), a2.result())


loop = asyncio.get_event_loop()
loop.run_until_complete(main())
print("over")
页: [1] 2
查看完整版本: python之asyncio协程的回调函数返回值如何获取