吾爱破解 - 52pojie.cn

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

查看: 1152|回复: 12
收起左侧

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

[复制链接]
sxfxtf 发表于 2022-8-31 13:14
[Python] 纯文本查看 复制代码
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([a1,a2])
    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
[Python] 纯文本查看 复制代码
print(a1.result(), a2.result()) 
 楼主| sxfxtf 发表于 2022-8-31 13:56
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()

可以参考一下这个,另外,建议了解一下coroutinetask的区别
我之前看b站高天的视频,大概记录了一些

视频链接

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

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

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。

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,可以异步执行

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),也可以异步执行

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的序列是可以的。
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的序列,不能是单个task。

    # 这里说明dones,其实是完成的task
    if task in dones:
        result = task.result()
        print(result)
    print('='*30)
    tasks = [asyncio.create_task(foo(random.randint(0, 100))) for _ in range(5)]
    dones, pendings = await asyncio.wait(tasks)
    for task in dones:
        print(task.result())

asyncio.run(main())

await机制详解

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

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的值,则设置为StopIterationException的value
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([task,task2])
    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([task,future], 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
[md]```
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
参考下这个代码
[Python] 纯文本查看 复制代码
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
参考下这个代码
[mw_shl_code=python,true]import time
import asyncio

这个代码其实和我写的一样  都是利用add_done_callback在协程函数运行完毕后传值给call_back函数.运行print是没问题.但是如果把你的call_back最后return一个值的话.就获取不到.
grekevin 发表于 2022-8-31 17:44
这样写执行效果是一样的
[Python] 纯文本查看 复制代码
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([a1, a2])
    print(a1.result(), a2.result())


loop = asyncio.get_event_loop()
loop.run_until_complete(main())
print("over")
您需要登录后才可以回帖 登录 | 注册[Register]

本版积分规则

返回列表

RSS订阅|小黑屋|处罚记录|联系我们|吾爱破解 - LCG - LSG ( 京ICP备16042023号 | 京公网安备 11010502030087号 )

GMT+8, 2024-11-1 06:55

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表