【问题标题】:Asyncio python: How to call object methods in coroutines?Asyncio python:如何在协程中调用对象方法?
【发布时间】:2020-10-18 20:29:29
【问题描述】:

我目前正在尝试做这样的事情:

import asyncio

class Dummy:
    def method(self):
        return 1
    def __str__(self):
        return "THIS IS A DUMMY CLASS"

async def start_doing():
    asyncio.sleep(1)
    return Dummy


async def do_something():
    return start_doing().method()


async def main():
    a = asyncio.create_task(do_something())
    b = asyncio.create_task(do_something())

    results = await asyncio.gather(a, b)
    print(results)

asyncio.run(main())

但我收到此错误:

AttributeError: 'coroutine' object has no attribute 'method'

这表明我不能在协程对象上调用我的方法。解决此问题的一种方法是执行以下操作:

async def do_something():
    return (await start_doing()).method()

但我认为通过这样做,您本质上就是使您的代码同步。您不是在生成未来,而是在 do_something 中等待您的工作完成,然后继续执行队列中的下一项。

awaitable 已被等待并且我的对象已准备好时,我应该如何调用对象方法?如何安排它在未来被调用?

【问题讨论】:

    标签: python python-3.x asynchronous async-await python-asyncio


    【解决方案1】:

    等待调用,然后在返回的类的实例上调用方法。

    async def start_doing():
        await asyncio.sleep(1)
        return Dummy
    
    
    async def do_something():
        thing = await start_doing()
        return thing().method()
    

    当 awaitable 已被等待并且我的对象已准备好时,我应该如何在将来调用对象方法?如何安排它在未来被调用?

    看看我能不能把这个弄对。

    • 当您创建任务 do_something 时,它会被安排。
    • 最终,事件循环决定让do_something 启动。
    • do_something 调用 start_doing 并等待它。
    • do_something 等待时,事件循环会从它手中夺走控制权,让其他东西轮流。
    • 在某些时间点start_doing 被调度、启动、完成等待/休眠、返回对象
      • 在等待/睡眠部分控制可能已被事件循环转移到其他任务
    • 最终在do_something 完成等待后,事件循环将控制/焦点交还给它。
      • 暗示其他计划任务已完成或正在等待某事。

    我真的不知道调度算法是什么,我认为它是一个循环的事情,但它可能比这更智能。

    【讨论】:

    • 不会使用await 让协程等待被调用函数的结果并因此以阻塞方式运行它吗?
    • 请注意,您也可以写(await start_doing())().method()(但我仍然更喜欢这个答案提出的更详细的方法)。
    • 不,使用await 是使它成为非阻塞的原因。当您致电 await x 时,您只需说“我正在等待 x 随时安排其他事情”。
    • @cglacet - 我回滚了你的编辑 - 从start_doing 返回一个实例会破坏do_something
    • 是的,对不起,我刚刚看到了,这看起来很奇怪,我的评论在那种情况下也没有意义:D。啊,我已经能够及时编辑了。
    【解决方案2】:

    要进一步扩展@wwiianswer 并解决使用await 的阻塞风险问题,您可以使用以下f 之类的功能:

    import time
    from datetime import datetime
    import asyncio
    
    start = datetime.now()
    
    async def f(x, block_for=3):
        print(f"{x} IN     {ellapsed_time()}s")
        time.sleep(block_for)
        print(f"{x} AWAIT  {ellapsed_time()}s")
        await asyncio.sleep(x)
        print(f"{x} OUT    {ellapsed_time()}s")
        
    def ellapsed_time():
        return (datetime.now() - start).seconds
        
    
    asyncio.create_task(f(2))
    asyncio.create_task(f(1))
    

    产生:

    2 IN     0s
    2 AWAIT  3s
    1 IN     3s
    1 AWAIT  6s
    2 OUT    6s
    1 OUT    7s
    

    在调用await 之前,f(2) 处于阻塞状态(阻止任何其他任务被调度)。一旦我们调用await,我们就明确地通知调度程序我们正在等待某些东西(通常是I/O,但这里是simply "sleeping")。同样,f(1) 阻止 f(2) 出去,直到它调用 await

    如果我们删除阻塞部分(0s 的阻塞)f(1) 将被重新安排在f(2) 之前执行,因此将首先完成:

    >>> start = datetime.now()
    >>> asyncio.create_task(f(2, block_for=0))
    >>> asyncio.create_task(f(1, block_for=0))
    
    2 IN     0s
    2 AWAIT  0s
    1 IN     0s
    1 AWAIT  0s
    1 OUT    1s
    2 OUT    2s
    

    最后,关于这部分:

    …如何安排它在未来被调用?

    你可以看看how is asyncio.sleep() in python implemented?,它可能会帮助你更好地理解异步编程的工作原理。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2015-12-02
      • 1970-01-01
      • 2020-10-16
      • 2016-03-04
      • 2020-12-29
      • 1970-01-01
      • 1970-01-01
      • 2018-02-25
      相关资源
      最近更新 更多