【问题标题】:How to create an event loop with rolling coroutines running on it forever?如何创建一个永远在其上运行滚动协程的事件循环?
【发布时间】:2015-08-08 06:25:46
【问题描述】:

为了防止上下文切换,我想创建一个大循环来服务网络连接和一些例程。

下面是普通函数的实现:

import asyncio
import time


def hello_world(loop):
    print('Hello World')
    loop.call_later(1, hello_world, loop)

def good_evening(loop):
    print('Good Evening')
    loop.call_later(1, good_evening, loop)

print('step: asyncio.get_event_loop()')
loop = asyncio.get_event_loop()

print('step: loop.call_soon(hello_world, loop)')
loop.call_soon(hello_world, loop)
print('step: loop.call_soon(good_evening, loop)')
loop.call_soon(good_evening, loop)

try:
    # Blocking call interrupted by loop.stop()
    print('step: loop.run_forever()')
    loop.run_forever()
except KeyboardInterrupt:
    pass
finally:
    print('step: loop.close()')
    loop.close()

这是协程的实现:

import asyncio


@asyncio.coroutine
def hello_world():
    while True:
        yield from asyncio.sleep(1)
        print('Hello World')

@asyncio.coroutine
def good_evening():
    while True:
        yield from asyncio.sleep(1)
        print('Good Evening')

print('step: asyncio.get_event_loop()')
loop = asyncio.get_event_loop()
try:
    print('step: loop.run_until_complete()')
    loop.run_until_complete(asyncio.wait([
        hello_world(),
        good_evening()
    ]))
except KeyboardInterrupt:
    pass
finally:
    print('step: loop.close()')
    loop.close()

还有混合的:

import asyncio
import time


def hello_world(loop):
    print('Hello World')
    loop.call_later(1, hello_world, loop)

def good_evening(loop):
    print('Good Evening')
    loop.call_later(1, good_evening, loop)

@asyncio.coroutine
def hello_world_coroutine():
    while True:
        yield from asyncio.sleep(1)
        print('Hello World Coroutine')

@asyncio.coroutine
def good_evening_coroutine():
    while True:
        yield from asyncio.sleep(1)
        print('Good Evening Coroutine')

print('step: asyncio.get_event_loop()')
loop = asyncio.get_event_loop()

print('step: loop.call_soon(hello_world, loop)')
loop.call_soon(hello_world, loop)
print('step: loop.call_soon(good_evening, loop)')
loop.call_soon(good_evening, loop)
print('step: asyncio.async(hello_world_coroutine)')
asyncio.async(hello_world_coroutine())
print('step: asyncio.async(good_evening_coroutine)')
asyncio.async(good_evening_coroutine())

try:
    loop.run_forever()
except KeyboardInterrupt:
    pass
finally:
    print('step: loop.close()')
    loop.close()

如您所见,每个协程函数都有一个 while 循环。我怎样才能让它像正常的一样? IE。完成后,在给定的延迟时间后调用自身,但不只是在那里放一个循环。

【问题讨论】:

  • 你为什么不喜欢循环?带有循环的代码非常明显且易于阅读。

标签: python python-3.x asynchronous coroutine python-asyncio


【解决方案1】:

您是否真的尝试运行您提供的三个示例?行为上的差异非常明显。

由于您从未说出您的期望,因此无法分辨什么是正确的,什么不是。这三种实现可能是对的,也可能是错的。我可以告诉你每个实现有什么行为,以及为什么它有这样的行为;只有你知道它是否正确。


在第二个示例(yield from asyncio.sleep(1))中,两个协程同时运行。这意味着每个人都将自己执行; hello_world 每秒打印一次Hello World,good_evening 每秒打印一次Good Evening。

另外两个例子都使用time.sleep(1),这是阻塞的。这意味着当第一个函数(无论是哪个;假设它是 hello_world)到达 time.sleep(1) 时,整个程序将挂起一秒钟。这意味着当hello_world 休眠时,good_evening 也无法运行,反之亦然。

程序执行如下:

  1. 循环进入。
  2. 循环调用hello_world。
  3. 到达hello_world 中的time.sleep(1)。程序休眠一秒钟。
  4. Hello World 已打印。
  5. hello_world 收益。
  6. 循环调用good_evening。
  7. Good Evening 已打印。
  8. 到达good_evening 中的time.sleep(1)。程序休眠一秒钟。
  9. good_evening 收益。
  10. 转到 2。

因此Hello World 和Good Evening 都每两 秒出现一次,因为在每个print 之间有两个time.sleep(1) 调用。如果您运行这两个示例,您会很容易注意到这一点。

【讨论】:

  • 谢谢@uranusjr。我编写了代码并运行了它。我知道它的流程,也知道 sleep 会阻塞线程并从异步中产生。不是。我不确定这种方式是正确的还是pythonic的方式。我知道协程的概念,但以前没有使用过。到目前为止,我还没有看到其他人为此目的编写相同的代码。他们使用普通函数和协程之一,但不能同时使用。
  • 顶级协程不做异步程序;仅当您使用 asyncio.sleep 之类的异步 API 时,程序才是异步的。您可以在协程中使用阻塞 API(例如 time.sleep),但阻塞 API 会阻塞整个程序,包括其他协程。是否可以接受取决于您的用例。
  • 再次感谢您。我已经更新了问题和代码。将 call_soon 替换为 call_later 并删除 time.sleep()。
  • 更新后的示例对我来说很有意义。现在示例 1 和示例 2 的行为应该相同,示例 3 也是真正的异步。
  • 谢谢@uranusjr。但我想知道的是如何发出while循环。
【解决方案2】:
import asyncio


@asyncio.coroutine
def hello_world_coroutine():
    yield from asyncio.sleep(1)
    print('Hello World Coroutine')
    yield from hello_world_coroutine()

@asyncio.coroutine
def good_evening_coroutine():
    yield from asyncio.sleep(1)
    print('Good Evening Coroutine')
    yield from good_evening_coroutine()

print('step: asyncio.get_event_loop()')
loop = asyncio.get_event_loop()
try:
    print('step: loop.run_until_complete()')
    loop.run_until_complete(asyncio.wait([
        hello_world_coroutine(),
        good_evening_coroutine()
    ]))
except KeyboardInterrupt:
    pass
finally:
    print('step: loop.close()')
    loop.close()

UPD

此代码将达到最大递归深度。可能是因为 Python 没有尾调用优化。将此处的代码作为错误示例。

【讨论】:

  • 我不会立即将其标记为答案。等待一段时间以获得更好的解决方案。
  • 这实际上是行不通的——最终你会达到最大递归深度,程序会崩溃。
【解决方案3】:

如果你真的想从协程中消除 while 循环(我不知道你为什么觉得这是必要的;这是做你想做的最自然的方式),你可以使用 asyncio.async (或asyncio.ensure_future 在 Python 3.4.4+ 上)安排协程在下一次事件循环迭代中再次运行:

import asyncio

@asyncio.coroutine
def hello_world():
    yield from asyncio.sleep(1)
    print('Hello World')
    asyncio.async(hello_world())

@asyncio.coroutine
def good_evening():
    yield from asyncio.sleep(1)
    print('Good Evening')
    asyncio.async(good_evening())

print('step: asyncio.get_event_loop()')
loop = asyncio.get_event_loop()
try:
    print('step: loop.run_until_complete()')
    asyncio.async(hello_world())
    asyncio.async(good_evening())
    loop.run_forever()
except KeyboardInterrupt:
    pass
finally:
    print('step: loop.close()')
    loop.close()

请注意,如果这样做,您必须切换回使用loop.run_forever(),因为hello_world/good_evening 将在打印后立即退出。

【讨论】:

  • 这就是我想要的。谢谢!
【解决方案4】:
# asyncio_coroutine_forever.py

import asyncio

async def hello_world():

    await asyncio.sleep(1)
    print('Hello World')
    await good_evening()


async def good_evening():

    await asyncio.sleep(1)
    print('Good Evening')
    await hello_world()


loop = asyncio.get_event_loop()

try:

    loop.run_until_complete(hello_world())
    loop.run_until_complete(good_evening())
    loop.run_forever()

finally:

    print('closing event loop')
    loop.close()

【讨论】:

  • 看起来好多了。谢谢莫顿。使用 Python 3.6.1 测试了该代码。
  • 尽管这最终会因为等待而耗尽堆栈帧。最好 await asyncio.sleep(1.0) 将 coros 作为期货添加到事件循环中。
猜你喜欢
  • 2023-03-19
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2022-01-11
  • 1970-01-01
  • 1970-01-01
  • 2011-03-06
  • 1970-01-01
相关资源
最近更新 更多