asyncio:有没有办法在不使用yield from的情况下释放事件循环的控制权?
我在想有没有办法在一个函数里释放 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 个回答
你可以看看Motor库使用的解决方案。简单来说,他们用一种叫做绿色线程(greenlets)的东西来让同步代码变得“异步”。
这段话主要在说 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
完成之前把控制权交还给事件循环,这样就不需要修改代码。