简体   繁体   English

如何等待 asyncio stream read() 直到当前协程完成读取

[英]How to wait asyncio stream read() until current coroutine finished reading

I'm using this example provided in python docs on asyncio streams, to write data and read from a socket.我正在使用python docs on asyncio 流中提供的这个示例来写入数据并从套接字读取。

I am consuming rabbitmq and sending the messages through the socket and waiting for a response.我正在使用rabbitmq并通过套接字发送消息并等待响应。 I've setup the reader and writer in __init__() :我在__init__()中设置了readerwriter器:


    self.reader, self.writer = await asyncio.open_connection(self.host, self.port, loop=self.loop)

and in consuming messages, I just send the message to the socket and read the response, then publish the response to another queue (after some processing):在消费消息时,我只是将消息发送到套接字并读取响应,然后将响应发布到另一个队列(经过一些处理):

    async def process_airtime(self, message: aio_pika.IncomingMessage):
        async with message.process():
            logger.info('Send: %r' % message.body)
            self.writer.write(message.body)

            data = await self.reader.read(4096)
            logger.info('Received: %r' % data)

            await self.publish(data) # publishing to some other queue

The problem is when i try to consume like say 10 messages, all other messges raise this error, although the last message successfully gets a response..问题是当我尝试使用 10 条消息时,所有其他消息都会引发此错误,尽管最后一条消息成功得到了响应..

RuntimeError: read() called while another coroutine is already waiting for incoming data

This is the response i get(i've truncated some response..):这是我得到的回应(我已经截断了一些回应..):

2022-02-05 10:59:17,123 INFO [__main__:130] request_consumer [*] waiting for messages...
Send: b'\xf2<D\xc1\x08\xe0\x90(\x00\x00\x00"000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000'
Send: b'\xf2<D\xc1\x08\xe0\x90(\x00\x00\x00"000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000'
Send: b'\xf2<D\xc1\x08\xe0\x90(\x00\x00\x00"000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000'

...


2022-02-05 10:59:17,194 ERROR [asyncio:1707] base_events Task exception was never retrieved
future: <Task finished name='Task-53' coro=<consumer() done, defined at /home/steven/workspace/python/onfon/cheka/venv/lib/python3.8/site-packages/aio_pika/queue.py:25> exception=RuntimeError('read() called while another coroutine is already waiting for incoming data')>
Traceback (most recent call last):
  File "/home/steven/workspace/python/onfon/cheka/venv/lib/python3.8/site-packages/aio_pika/queue.py", line 27, in consumer
    return await create_task(callback, message, loop=loop)
  File "request_consumer.py", line 122, in process_airtime
    data = await self.reader.read(4096)
  File "/usr/lib/python3.8/asyncio/streams.py", line 684, in read
    await self._wait_for_data('read')
  File "/usr/lib/python3.8/asyncio/streams.py", line 503, in _wait_for_data
    raise RuntimeError(
RuntimeError: read() called while another coroutine is already waiting for incoming data
2022-02-05 10:59:17,194 ERROR [asyncio:1707] base_events Task exception was never retrieved
future: <Task finished name='Task-54' coro=<consumer() done, defined at /home/steven/workspace/python/onfon/cheka/venv/lib/python3.8/site-packages/aio_pika/queue.py:25> exception=RuntimeError('read() called while another coroutine is already waiting for incoming data')>
Traceback (most recent call last):
  File "/home/steven/workspace/python/onfon/cheka/venv/lib/python3.8/site-packages/aio_pika/queue.py", line 27, in consumer
    return await create_task(callback, message, loop=loop)
  File "request_consumer.py", line 122, in process_airtime
    data = await self.reader.read(4096)
  File "/usr/lib/python3.8/asyncio/streams.py", line 684, in read
    await self._wait_for_data('read')
  File "/usr/lib/python3.8/asyncio/streams.py", line 503, in _wait_for_data
    raise RuntimeError(
RuntimeError: read() called while another coroutine is already waiting for incoming data
Received: b'\x0160210\xf2>D\x95\n\xe0\x80 \x00\x00\x00\x00\x00\x00\x00"0000000000000000000000000000000000000000000000'

My question is, what should i, probably, do to make read() be called again only after a current coroutine read ing has finished.我的问题是,我应该怎么做才能使 read() 仅在当前协程read完成后再次调用。 Will that affect performance or is there some way i can read on, say, different threads?这会影响性能还是我可以通过某种方式阅读不同的线程?

I will appreciate if someone points me the right direction.如果有人指出我正确的方向,我将不胜感激。 I use python3.8 on linux我在 linux 上使用 python3.8

Simple answer is to only read() from one task.简单的答案是只从一项任务中read()

It sounds like you are using a callback to consume RMQ messages.听起来您正在使用回调来使用 RMQ 消息。 If so, then aio_pika will consume messages asynchronously (ie. concurrently) if it has a multiple messages.如果是这样,那么如果 aio_pika 有多个消息,它将异步(即并发)消费消息。 That is, it will create a new task for each callback/message and leave it to its own devices.也就是说,它将为每个回调/消息创建一个新任务并将其留给自己的设备。

Given that you have a read() whilst processing a message, that doesn't really make sense for your read calls.鉴于您在处理消息时有一个 read() ,这对您的 read 调用没有意义。 How will you know which read is for which message.你怎么知道哪个读是针对哪个消息的。 You need to find some way to sync your reads to each message.您需要找到某种方法将您的阅读内容同步到每条消息。 There are a few ways you can do this:有几种方法可以做到这一点:

  • Put a lock around calls to read()read()的调用加锁
  • Creating a separate task that is solely responsible for calling read() , it puts the results onto a queue, from which any task can read.创建一个单独负责调用read()的单独任务,它将结果放入队列中,任何任务都可以从中读取。 asyncio queues are task safe (unlike read() ). asyncio 队列是任务安全的(与read()不同)。
  • Or perhaps most simply, by using the queue as an iterator (and not spawning a new task to handle the message).或者最简单的方法是使用队列作为迭代器(而不是生成新任务来处理消息)。

I am assuming you current code looks something a bit like:我假设您当前的代码看起来有点像:

async def init_rmq_consumer():
    # connect to RMQ and create a queue
    ...
    queue = ...

    # start consuming messages off of the queue
    await queue.consume(process_airtime)

if __name__ == '__main__':
    loop = asyncio.get_event_loop()
    loop.run_until_complete(init_rmq_consumer())
    loop.run_forever()

Using the queue as an iterator might look something like:使用队列作为迭代器可能看起来像:

async def main():
    # connect to RMQ and create a queue
    ...
    queue = ...

    # start consuming messages off of the queue *serially*
    async with queue.iterator() as queue_iter:
         async for message in queue_iter:
              # we will not fetch a new message from the queue until we are
              # finished with this one.
              await process_airtime(message)

if __name__ == '__main__':
    asyncio.run(main())

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM