【问题标题】:Gradually create async tasks and wait for all of them to complete逐步创建异步任务并等待所有任务完成
【发布时间】:2016-01-15 07:24:34
【问题描述】:

我正在尝试编写一个程序来与我创建的服务器建立大量网络套接字连接:

class WebSocketClient():

    @asyncio.coroutine
    def run(self):
        print(self.client_id, 'Connecting')
        ws = yield from aiohttp.ws_connect(self.url)
        print(self.client_id, 'Connected')
        print(self.client_id, 'Sending the message')
        ws.send_str(self.make_new_message())

        while not ws.closed:
            msg = yield from ws.receive()

            if msg.tp == aiohttp.MsgType.text:
                print(self.client_id, 'Received the echo')
                yield from ws.close()
                break

        print(self.client_id, 'Closed')


@asyncio.coroutine
def make_clients():

    for client_id in range(args.clients):
        yield from WebSocketClient(client_id, WS_CHANNEL_URL.format(client_id=client_id)).run()


event_loop.run_until_complete(make_clients())

问题是所有的客户都是一个接一个地工作:

0 Connecting
0 Connected
0 Sending the message
0 Received the echo
0 Closed
1 Connecting
1 Connected
1 Sending the message
1 Received the echo
1 Closed
...

我尝试使用asyncio.wait,但所有客户端都是一起启动的。我希望它们逐渐创建并在每个创建后立即连接到服务器。同时继续创建新客户。

我应该采用什么方法来实现这一点?

【问题讨论】:

标签: python python-3.x python-asyncio aiohttp


【解决方案1】:

使用asyncio.wait 是一个好方法。你可以结合asyncio.ensure_futureasyncio.sleep来逐步创建任务:

@asyncio.coroutine
def make_clients(nb_clients, delay):
    futures = []
    for client_id in range(nb_clients):
        url = WS_CHANNEL_URL.format(client_id=client_id)
        coro = WebSocketClient(client_id, url).run()
        futures.append(asyncio.ensure_future(coro))
        yield from asyncio.sleep(delay)
    yield from asyncio.wait(futures)

编辑:我实现了一个 FutureSet 类,它应该做你想做的事。该集合可以填充期货并在完成后自动删除它们。也可以等待所有期货完成。

class FutureSet:

    def __init__(self, maxsize, *, loop=None):
        self._set = set()
        self._loop = loop
        self._maxsize = maxsize
        self._waiters = []

    @asyncio.coroutine
    def add(self, item):
        if not asyncio.iscoroutine(item) and \
           not isinstance(item, asyncio.Future):
            raise ValueError('Expecting a coroutine or a Future')
        if item in self._set:
            return
        while len(self._set) >= self._maxsize:
            waiter = asyncio.Future(loop=self._loop)
            self._waiters.append(waiter)
            yield from waiter
        item = asyncio.async(item, loop=self._loop)    
        self._set.add(item)
        item.add_done_callback(self._remove)

    def _remove(self, item):
        if not item.done():
            raise ValueError('Cannot remove a pending Future')
        self._set.remove(item)
        if self._waiters:
            waiter = self._waiters.pop(0)
            waiter.set_result(None)

    @asyncio.coroutine
    def wait(self):
        return asyncio.wait(self._set)

例子:

@asyncio.coroutine
def make_clients(nb_clients, limit=0):
    futures = FutureSet(maxsize=limit)
    for client_id in range(nb_clients):
        url = WS_CHANNEL_URL.format(client_id=client_id)
        client = WebSocketClient(client_id, url)
        yield from futures.add(client.run())
    yield from futures.wait()

【讨论】:

  • asyncio.Queue 是一个不用于继承的 final 类。因此,即使技术上可行,用户也不应该从 asyncio.Queue 派生自己的类。
  • @AndrewSvetlov 我猜用户可能希望从asyncio.Queue 继承来创建不同类型的队列(例如asyncio.PriorityQueueasyncio.LifoQueue),但在这种情况下我只是懒惰:p无论如何我都摆脱了它。
  • 不,用户不能(至少不应该)。 LifoQueuePriorityQueueasyncio 类,不用于继承。唯一用于继承的asyncio 类是Protocol 和family。在我们设计图书馆时,Guido van Rossum 多次宣布该州。
  • @Vincent 您的上一个版本也可以使用。谢谢!你认为在while len(self._set) >= self._maxsize 循环中的某处添加asyncio.sleep 会减少100% 的CPU 负载吗?
  • 我想补充一点,asyncio.wait 返回 2 组期货 - 完成和待定。从这个例子来看,这对我来说并不是很明显,让我感到惊讶(直到我阅读了文档)。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-05-26
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多