使用asyncio

时间:2017-09-08 15:30:31

标签: python-3.x python-asyncio

在尝试使用select()(在Python 3.6上)实现类似asyncio的功能时,我遇到了一些我无法理解的行为:

  • 我有2个队列( A & B ),其中消息由异步生产者Queue.put
  • 我有一个异步使用者(选择器),它会轮询队列并使用select()获取第一个可用的消息(即asyncio.wait_for类功能。

民意调查就像这样:

poll = list(_.get() for _ in queues)
while True:
    done, pending = await asyncio.wait(tuple(poll), return_when=asyncio.FIRST_COMPLETED)

然后,迭代done期货,我用poll替换Queue.get中的相应条目:

    for f in done:
        try:
            i = poll.index(f._coro)
            ...
            poll[i] = queues[i].get()

现在,我遇到的奇怪行为是选择有效,但部分done期货.result()返回None,并且通过队列发送的消息将丢失:< / p>

PUT B ('B', 0)/0
GET B ('B', 0)/0
PUT A ('A', 0)/0
GET A None/0         << ('A', 0) is never received
PUT A ('A', 1)/0
GET A ('A', 1)/0
PUT B ('B', 1)/0
GET B None/0
PUT A ('A', 2)/0
GET A None/0
PUT B ('B', 2)/0
GET B None/0

这是代码

#!/usr/bin/env python3
import asyncio, random

async def queue_generator( name, queue, speed=1 ):
    counter = 0
    while True:
        t = (random.random() + 0.5) * speed
        await asyncio.sleep(t)
        m = (name, counter)
        print ("PUT {0} {1}/{2:d}".format(name, m, queue.qsize()))
        queue.put_nowait(m)
        counter += 1

async def select( *queues ):
    poll = list(_.get() for _ in queues)
    while True:
        # That's the select()-like functionalit
        done, pending = await asyncio.wait(tuple(poll), return_when=asyncio.FIRST_COMPLETED)
        for f in done:
            i = poll.index(f._coro)
            # That's where sometimes v is None
            v = f.result()
            print ("GET {0} {1}/{2}".format("AB"[i], v, queues[i].qsize()))
            poll[i] = queues[i].get()
        await asyncio.sleep(0.5)

if __name__ == "__main__":
    loop     = asyncio.get_event_loop()
    queue_a  = asyncio.Queue(10)
    queue_b  = asyncio.Queue(10)
    tasks = (
        loop.create_task(queue_generator("A", queue_a, 3)),
        loop.create_task(queue_generator("B", queue_b, 3)),
        loop.create_task(select(queue_a, queue_b))
    )
    loop.run_until_complete(asyncio.wait(tasks))
    loop.close()

当发生当前有消息的队列发生变化时会发生这种情况,我认为问题在于对pending期货做些什么。实际上,添加

    for f in pending:
        f.cancel()

解决了这个问题,但我想尽可能多地重复利用这些未来。我想这个问题来自于asyncio.wait_for将生成器列表静默转换为任务这一事实。

1 个答案:

答案 0 :(得分:1)

我没有深入研究会发生什么,但移动poll创建内部循环似乎解决了问题:

async def select( *queues ):
    while True:    
        poll = list(_.get() for _ in queues)  # HERE    
        # That's the select()-like functionalit
        done, pending = await asyncio.wait(tuple(poll), return_when=asyncio.FIRST_COMPLETED)

poll是协程列表。每个独特的协程通常都应该等待一次。否则可能导致奇怪的事情。