简体   繁体   English

Python3 Asyncio从初始连接创建辅助连接

[英]Python3 Asyncio Creating a secondary connection from an initial one

I am trying to add two coroutines to asyncio loop and getting an error: 我试图将两个协程添加到asyncio循环并得到一个错误:

 RuntimeError: This event loop is already running 

My objective is to communicate to a server (that I have no control of). 我的目标是与服务器通信(我无法控制)。 This server expects an initial connection from the client. 该服务器期望来自客户端的初始连接。 The server then provided a port to the client on this connection. 然后,服务器在此连接上为客户端提供端口。 The client has to use this port to create a second connection. 客户端必须使用此端口创建第二个连接。 This second connection is used by the server to send unsolicited messages to the client. 服务器使用此第二个连接向客户端发送未经请求的消息。 The first connection remains up throughout for other two-way communications. 对于其他双向通讯,第一个连接始终保持打开状态。

To recreate this scenario, I have some code that reproduces the error: 要重新创建这种情况,我有一些代码可以重现该错误:

class Connection():
    def __init__(self, ip, port, ioloop):
        self.ip = ip
        self.port = port
        self.ioloop = ioloop
        self.reader, self.writer = None, None
        self.protocol = None
        self.fileno = None

    async def __aenter__(self):
        # Applicable when doing 'with Connection(...'
        log.info("Entering and Creating Connection")
        self.reader, self.writer = (
            await asyncio.open_connection(self.ip, self.port, loop=self.ioloop)
        )
        self.protocol = self.writer.transport.get_protocol()
        self.fileno = self.writer.transport.get_extra_info('socket').fileno()

        log.info(f"Created connection {self}")
        return self

    async def __aexit__(self, *args):
        # Applicable when doing 'with Connection(...'
        log.info(f"Exiting and Destroying Connection {self}")
        if self.writer:
            self.writer.close()

    def __await__(self):
        # Applicable when doing 'await Connection(...'
        return self.__aenter__().__await__()

    def __repr__(self):
        return f"[Connection {self.ip}:{self.port}, {self.protocol}, fd={self.fileno}]"

    async def send_recv_message(self, message):
        log.debug(f"send: '{message}'")
        self.writer.write(message.encode())
        await self.writer.drain()

        log.debug("awaiting data...")
        data = await self.reader.read(9999)
        data = data.decode()
        log.debug(f"recv: '{data}'")
        return data


class ServerConnection(Connection):
    async def setup_connection(self):
        event_port = 8889  # Assume this came from the server
        print("In setup connection")
        event_connection = await EventConnection('127.0.0.1', event_port, self.ioloop)
        self.ioloop.run_until_complete(event_connection.recv_message())

class EventConnection(Connection):
    async def recv_message(self):
        log.debug("awaiting recv-only data...")
        data = await self.reader.read(9999)
        data = data.decode()
        log.debug(f"recv only: '{data}'")
        return data


async def main(loop):
    client1 = await ServerConnection('127.0.0.1', 8888, loop)
    await client1.setup_connection()
    await client1.send_recv_message("Hello1")
    await client1.send_recv_message("Hello2")
    await asyncio.sleep(5)

if __name__ == '__main__':
    #logging.basicConfig(level=logging.INFO)
    logging.basicConfig(level=logging.DEBUG)
    log = logging.getLogger()
    ioloop = asyncio.get_event_loop()
    print('starting loop')
    ioloop.run_until_complete(main(ioloop))
    print('completed loop')
    ioloop.close()

The error occurs in ServerConnection.setup_connection() method where run_until_complete is being called. 该错误发生在ServerConnection.setup_connection()方法中,其中调用run_until_complete。

I am probably doing something wrong due to lack of understanding asyncio. 由于缺乏对异步的了解,我可能做错了。 Basically, how do I setup a secondary connection which will get event notifications (unsolicited) while setting up the first connection? 基本上,我该如何设置辅助连接,该辅助连接在设置第一个连接时会收到事件通知(未经请求)?

Thanks. 谢谢。

Followup 跟进

Since the code is very similar (a few changes to add more functionality to it), I hope it's not bad etiquette to followup to the original post as the resulting error is still the same. 由于代码非常相似(需要进行一些更改以添加更多功能),所以我希望跟进原始帖子的礼节还不错,因为导致的错误仍然相同。

The new issue is that when it receives the unsolicited message (which is received by EventConnection), the recv_message calls process_data method. 新问题是,当接收到未经请求的消息(EventConnection收到)时,recv_message会调用process_data方法。 I would like to make process_data be a future so that recv_message completes (ioloop should stop). 我想使process_data成为未来,以便recv_message完成(ioloop应该停止)。 The ensure_future would then pick it up and continue running again to use ServerConnection to do a request/response to the server. 然后,suresure_future将对其进行拾取并再次运行,以使用ServerConnection对服务器进行请求/响应。 Before it does that though, it has to go to some user code (represented by external_command() and from whom I would prefer to hide the async stuff). 但是,在此之前,它必须转到一些用户代码(由external_command()表示,我希望从这些用户代码中隐藏异步内容)。 This would make it synchronous again. 这将使其再次同步。 Hence, once they've done what they need to, they should call execute_command on ServerConnection, which then kicks off the loop again. 因此,一旦完成所需的操作,就应该在ServerConnection上调用execute_command,然后再次启动循环。

The problem is, my expectation for using ensure_future didn't pan out as it seems the loop didn't stop from running. 问题是,我对使用guarantee_future的期望并未实现,因为似乎循环没有停止运行。 Hence, when the code execution reaches execute_command which does the run_until_complete, an exception with the error "This event loop is already running" occurs. 因此,当代码执行到达执行run_until_complete的execute_command时,将发生错误错误“此事件循环已在运行”。

I have two questions: 我有两个问题:

  1. How can I make it so that the ioloop can stop after process_data is placed into ensure_future, and subsequently be able to run it again in execute_command? 我该如何做,以便将process_data放入sure_future之后,ioloop可以停止,然后可以在execute_command中再次运行它?

  2. Once recv_message has received something, how can we make it so that it can receive more unsolicited data? 一旦recv_message收到了一些东西,我们如何制作它以便它可以接收更多不请自来的数据? Is it enough/safe to just use ensure_future to call itself again? 仅使用guarantee_future再次调用自身是否足够/安全?

Here's the example code that simulates this issue. 这是模拟此问题的示例代码。

client1 = None

class ServerConnection(Connection):
    connection_type = 'Server Connection'
    async def setup_connection(self):
        event_port = 8889  # Assume this came from the server
        print("In setup connection")
        event_connection = await EventConnection('127.0.0.1', event_port, self.ioloop)
        asyncio.ensure_future(event_connection.recv_message())

    async def _execute_command(self, data):
        return await self.send_recv_message(data)

    def execute_command(self, data):
        response_str = self.ioloop.run_until_complete(self._execute_command(data))
        print(f"exec cmd response_str: {response_str}")

    def external_command(self, data):
        self.execute_command(data)


class EventConnection(Connection):
    connection_type = 'Event Connection'
    async def recv_message(self):
        global client1
        log.debug("awaiting recv-only data...")
        data = await self.reader.read(9999)
        data = data.decode()
        log.debug(f"recv-only: '{data}'")
        asyncio.ensure_future(self.process_data(data))
        asyncio.ensure_future(self.recv_message())

    async def process_data(self, data):
        global client1
        await client1.external_command(data)


async def main(ioloop):
    global client1
    client1 = await ServerConnection('127.0.0.1', 8888, ioloop)
    await client1.setup_connection()
    print(f"after connection setup loop running is {ioloop.is_running()}")
    await client1.send_recv_message("Hello1")
    print(f"after Hello1 loop running is {ioloop.is_running()}")
    await client1.send_recv_message("Hello2")
    print(f"after Hello2 loop running is {ioloop.is_running()}")
    while True:
        print(f"inside while loop running is {ioloop.is_running()}")
        t = 10
        print(f"asyncio sleep {t} sec")
        await asyncio.sleep(t)


if __name__ == '__main__':
    logging.basicConfig(level=logging.DEBUG)
    log = logging.getLogger()
    ioloop = asyncio.get_event_loop()
    print('starting loop')
    ioloop.run_until_complete(main(ioloop))
    print('completed loop')
    ioloop.close()

Try replacing: 尝试更换:

self.ioloop.run_until_complete

With

await

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

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