asyncio:有没有办法在不使用yield from的情况下释放事件循环的控制权?

3 投票
2 回答
2184 浏览
提问于 2025-04-28 13:23

我在想有没有办法在一个函数里释放 asyncio 的控制循环一段时间,而不需要使用协程装饰器和 yield from 这些关键字?

import asyncio
import time


class MyClass(object):

    def do_something_periodically(self, delay, repeats):
        for i in range(repeats):
            # Do something useful
            self._sleep(delay)

    def _sleep(self, delay):
        time.sleep(delay)


class MyAsyncioClass(MyClass):

    def _sleep(self, delay):
        # Perform an asyncio.sleep(delay) here which yields control of the event loop
        # and waits for time delay before returning


loop = asyncio.get_event_loop()
obj1 = MyAsyncioClass()
obj2 = MyAsyncioClass()
loop.run_until_complete(asyncio.wait(
    [obj1.do_something_periodically(1000, 3),
     obj2.do_something_periodically(2000, 2)]))

我希望能做到这一点,这样 do_something_periodically 方法就可以从那些对 asyncio 一无所知的代码中调用,但在休眠期间会释放循环控制。这可能吗?

谢谢!

编辑:展示我特定用例的简化版本

暂无标签

2 个回答

0

你可以看看Motor库使用的解决方案。简单来说,他们用一种叫做绿色线程(greenlets)的东西来让同步代码变得“异步”。

6

这段话主要在说 asyncio 的工作方式。它使用的是一种明确的异步模型——如果代码要把控制权交还给事件循环,就必须使用 yield from,或者使用回调函数/Futures。如果你在一个函数里(比如 do_something_periodically),你不能在不使用 1) yield from 或 2) 完全退出这个方法的情况下,把控制权交还给事件循环。你可以在 asyncio 和非 asyncio 版本的类之间进行一些代码重用,但任何需要调用 coroutine 的方法,自己也必须是一个 coroutine

class MyClass(object):

    def do_something_periodically(self, delay, repeats):
        for i in range(repeats):
            self.do_something_useful()
            self._sleep(delay)

    def _sleep(self, delay):
        time.sleep(delay)

    def do_something_useful(self):
        # Do something useful here, which doesn't need to yield to the event loop

class MyAsyncioClass(MyClass):

    @asyncio.coroutine
    def do_something_periodically(self, delay, repeats):
        for i in range(repeats):
            self.do_something_useful()
            yield from self._sleep(delay)

    @asyncio.coroutine
    def _sleep(self, delay):
        yield from asyncio.sleep(delay)

不过,看起来你的具体用例可以用另一种方式解决,但那样做有点麻烦,还需要对 MyClass 的逻辑进行一些修改:

class MyClass(object):

    def do_something_periodically(self, delay, repeats, i=0):
        while i < repeats:
            # do something useful
            if not self._sleep(delay, repeats, i):
                break
            i+= 1
        return i

    def _sleep(self, delay, repeats, i):
        time.sleep(delay)
        return True

class MyAsyncioClass(MyClass):

    def do_something_periodically(self, delay, repeats, i=0):
        out = super().do_something_periodically(delay, repeats, i)
        if out == repeats:
            asyncio.get_event_loop().stop()

    def _sleep(self, delay, repeats, i):
        i+=1
        asyncio.get_event_loop().call_later(delay, 
                                            self.do_something_periodically, 
                                            delay, repeats, i)
        return False

我们使用 loop.call_later 来实现类似 asyncio.sleep 的功能,并调整 do_something_periodically,使其既能在正常情况下完全遍历 while 循环,也能在 asyncio 的情况下被反复调用,并且 i 的值会逐渐增加。

不幸的是,没有简单、可靠的方法可以让同一段代码同时适用于同步和 asyncio 的情况。这是像 asyncio/tornado 这样的明确异步框架的一个主要缺点,而 gevent 则使用了隐式异步模型。在 gevent 中,time.sleep(delay) 会被替换成一个 gevent 版本,这个版本会在 sleep 完成之前把控制权交还给事件循环,这样就不需要修改代码。

撰写回答