处理永远不会在python3 asyncio

时间:2015-11-04 18:47:19

标签: python-3.x python-asyncio

有时异步任务没有有意义的终止条件 - 例如,在下面的程序中,“rate_limiter”任务会以固定的速率永久地在队列上生成一个令牌流。

import asyncio
import sys

@asyncio.coroutine
def rate_limiter(queue, rate):
    """Push tokens to QUEUE at a rate of RATE per second."""
    delay = 1/rate
    while True:
        yield from asyncio.sleep(delay)
        yield from queue.put(None)

@asyncio.coroutine
def do_work(n, rate):
    for i in range(n):
        yield from rate.get()
        sys.stdout.write("job {}\n".format(i))

def main():
    loop   = asyncio.get_event_loop()
    rate   = asyncio.Queue()
    rltask = loop.create_task(rate_limiter(rate, 10))
    wtask  = loop.create_task(do_work(20, rate))
    loop.run_until_complete(wtask)

main()

这个程序完美地运行除了,asyncio库认为这是一个编程错误,只有在没有任何剩余的速率限制时才会丢弃rltask;你得到像

这样的投诉
...
job 18
job 19
Task was destroyed but it is pending!
task: <Task pending coro=<rate_limiter() running at rl.py:9>
      wait_for=<Future pending cb=[Task._wakeup()]>>

(无论是否处于调试模式)。

我可以用一个告诉rate_limiter协程突破其循环的事件来解决这个问题,但这感觉就像是额外的代码而没有真正的好处。在使用asyncio时,你如何假设处理这种情况?

编辑:我不清楚:我正在寻找的是类似线程上的daemon标志:这样做的东西让我不必等待一个特定的任务,理想地表示为任务本身或其协程的注释。我也会接受一个答案,证明没有这样的机制。我已经知道了解决方法。

2 个答案:

答案 0 :(得分:2)

要避免&#34;任务已被销毁,但它正在等待!&#34; 警告,如果您设置了虚拟结果,则可以在程序退出时标记永不结束的协程对于相应的未来对象:

#!/usr/bin/env python3.5
import asyncio
import itertools
from contextlib import closing, contextmanager


@contextmanager
def finishing(coro_or_future, *, loop=None):
    """Mark a never ending coroutine or future as done on __exit__."""
    fut = asyncio.ensure_future(
        coro_or_future, loop=loop)  # start infinite loop
    try:
        yield
    finally:
        if not fut.cancelled():
            fut.set_result(None)  # mark as finished


async def never_ends():
    for c in itertools.cycle('\|/-'):
        print(c, end='\r', flush=True)
        await asyncio.sleep(.3)


with closing(asyncio.get_event_loop()) as loop, \
     finishing(never_ends(), loop=loop):
    loop.run_until_complete(asyncio.sleep(3))  # do something else

它假定您的协程在进程退出之前不需要显式清理。在后一种情况下,为清理定义一个显式过程:提供可以调用的方法(例如,server.close()server.wait_closed()),或传递调用者应该调用的事件(asyncio.Event)设置为关闭,或引发异常(例如CancelledError)。

引入finishing()的好处是检测错误,即除非finishing()调用明确地将其静音,否则不应忽略该警告。

答案 1 :(得分:1)

.cancel()任务,然后等待它被取消,抓住外面的CancelledError

# vim: tabstop=4 expandtab

import asyncio
import sys

@asyncio.coroutine
def rate_limiter(queue, rate):
    """Push tokens to QUEUE at a rate of RATE per second."""
    delay = 1/rate
    while True:
        yield from asyncio.sleep(delay)
        yield from queue.put(None)

@asyncio.coroutine
def do_work(n, rate):
    for i in range(n):
        yield from rate.get()
        sys.stdout.write("job {}\n".format(i))

def main():
    loop   = asyncio.get_event_loop()
    rate   = asyncio.Queue()
    rltask = loop.create_task(rate_limiter(rate, 10))
    wtask  = loop.create_task(do_work(20, rate))
    loop.run_until_complete(wtask)
    rltask.cancel()
    try:
        loop.run_until_complete(rltask)
    except asyncio.CancelledError:
        ...
    loop.close()

main()