【问题标题】:Asyncio errors: CLI - python daemon example but has errors/undesired behavior on exitAsyncio 错误:CLI - python 守护程序示例,但在退出时出现错误/不良行为
【发布时间】:2021-05-25 04:15:34
【问题描述】:

我开发了一种模式,用于通过cmd 模块外壳使用eventloop 命令python 守护程序。但是,它还没有准备好,因为我无法弄清楚如何优雅地退出这两个应用程序(我还在学习asyncio 并且无法弄清楚以下问题)。当cmd 模块被命令退出时,我得到:

(Cmd) exit
Shutting down client...
Traceback (most recent call last):
  File "client_loop.py", line 10, in <module>
    loop.run_until_complete(test.cmdloop())
  File "...\asyncio\base_events.py", line 467, in run_until_complete
    future = tasks.ensure_future(future, loop=self)
  File "...\lib\asyncio\tasks.py", line 526, in ensure_future
    raise TypeError('An asyncio.Future, a coroutine or an awaitable is '
TypeError: An asyncio.Future, a coroutine or an awaitable is required

我不擅长asyncio,我做错了什么?很抱歉这个问题很长,但文件/错误使它很长,希望它更容易调试。

以下是支持文件:

shell.py

# implementation of the (Cmd) prompt with history functionality
# standard imports
import cmd as cmd

class Shell(cmd.Cmd):
    def __init__(self, **kwargs):
        cmd.Cmd.__init__(self, **kwargs)
        self.eventloop = None
        self.shutdown_client = None
        self.tcp_echo_client = None
    
    def set_eventloop(self, loop):
        self.eventloop = loop
    
    def set_funcs(self, tcp_echo_client, shutdown_client):
        self.tcp_echo_client = tcp_echo_client
        self.shutdown_client = shutdown_client

    def do_exit(self,*args):
        """
        Exits the shell gracefully
        :param args:
        :return:
        """
        print('Shutting down client...')
        self.shutdown_client(self.eventloop)
        return True

    def default(self, line):
        try:
            self.eventloop.run_until_complete(self.tcp_echo_client(line, self.eventloop))
        except SystemExit:
            pass

server.py

# server logic to parse arguments coming over the TCP socket and echo it back

# standard imports
import asyncio

async def handle_echo(reader, writer):
    data = await reader.read(100)
    message = data.decode()
    addr = writer.get_extra_info('peername')
    print("Received %r from %r" % (message, addr))

    print("Send: %r" % message)
    writer.write(data)
    await writer.drain()

    print("Close the client socket")
    writer.close()

loop = asyncio.get_event_loop()
coro = asyncio.start_server(handle_echo, '127.0.0.1', 8888, loop=loop)
server = loop.run_until_complete(coro)

# Serve requests until Ctrl+C is pressed
print('Serving on {}'.format(server.sockets[0].getsockname()))
try:
    loop.run_forever()
except KeyboardInterrupt:
    pass
finally:
    # Close the server
    for task in asyncio.Task.all_tasks():
        loop.run_until_complete(task)

    server.close()
    loop.run_until_complete(server.wait_closed())
    loop.stop()
    loop.close()
    exit(0)

client.py

# client functions to send message over TCP and process response
# standard imports
import asyncio

# user imports
import shell

async def tcp_echo_client(message, loop):
    reader, writer = await asyncio.open_connection('127.0.0.1', 8888,
                                                   loop=loop)

    print('Send: %r' % message)
    writer.write(message.encode())

    data = await reader.read(100)
    print('Received: %r' % data.decode())

    print('Close the socket')
    writer.close()

def shutdown_client(loop):
    loop.stop()

    # Find all running tasks:
    pending = asyncio.Task.all_tasks()

    # Run loop until tasks done:
    loop.run_until_complete(asyncio.gather(*pending))

loop = asyncio.get_event_loop()
test = shell.Shell()
test.set_eventloop(loop)
test.set_funcs(tcp_echo_client, shutdown_client)
loop.run_until_complete(test.cmdloop())
loop.close()

【问题讨论】:

  • 您能否将您的代码缩短为一个最小 示例,该示例仍然显示您所询问的异步问题?很难遵循程序的抽象层,这可能与错误无关。 (代码似乎也不完整;您调用test.cmdloop(),但似乎没有在任何地方定义cmdloop。)
  • cmdloop() 来自 cmd.Cmd。老实说,它尽可能地小。它的客户端/服务器,其中客户端是一个 shell,它们相互发送 argsparse 命令。这是我所追求并作为基础的模式。我可以删除 arg.py 但这几乎没有什么收获。您是否尝试过自己运行程序?您可能会惊讶于它是多么容易拿起它。
  • 好的,我接受了你的建议。我让它变得更简单,但仍然会产生同样的问题。请看一下。如果您启动客户端/服务器并开始在彼此之间发送消息,也应该可以工作。
  • 我真的不想听起来要求很高,也许其他人不会介意问题的格式,所以我写这个只是为了澄清:我指的是这样一个事实示例包含四个源文件和抽象,如 cmd.Cmd 显然与 asyncio 无关。提供一个最小示例的想法是您将代码范围缩小到尽可能接近问题所在的位置,以便更容易理解和解决它。即使在缩短之后,您的示例仍然有多个源文件并使用Cmd,因此它至少似乎不是最小的。
  • 入口点看起来很可疑 - 它应该如何工作,因为 cmdloop 不是异步的并且您将它传递给 run_until_complete()?此外,您正在其中运行 run_until_complete() 的其他实例。也许根本问题是cmd.Cmd 与 asyncio 不兼容?异步程序通常需要从头开始使用异步库,至少在调用任何可能阻塞的东西时。

标签: python-3.x shell tcp python-asyncio event-loop


【解决方案1】:

问题在于 cmd 不需要 asyncio 来处理来自用户的输入并通过 TCP/IP 套接字向其发送消息。只需从客户端删除 asyncio 即可解决问题。它仍然提供 shell 实现和客户端-服务器模式。这是新代码:

server.py

# server logic to parse arguments coming over the TCP socket and echo it back

# standard imports
import asyncio

async def handle_echo(reader, writer):
    data = await reader.read(100)
    message = data.decode()
    addr = writer.get_extra_info('peername')
    print("Received %r from %r" % (message, addr))

    print("Send: %r" % message)
    writer.write(data)
    await writer.drain()

    print("Close the client socket")
    writer.close()

loop = asyncio.get_event_loop()
coro = asyncio.start_server(handle_echo, '127.0.0.1', 8888, loop=loop)
server = loop.run_until_complete(coro)

# Serve requests until Ctrl+C is pressed
print('Serving on {}'.format(server.sockets[0].getsockname()))
try:
    loop.run_forever()
except KeyboardInterrupt:
    pass
finally:
    # Close the server
    for task in asyncio.Task.all_tasks():
        loop.run_until_complete(task)

    server.close()
    loop.run_until_complete(server.wait_closed())
    loop.stop()
    loop.close()
    exit(0)

client_shell.py

# implementation of a shell prompt (using Cmd module) to send message over TCP and process response
# standard imports
import socket
import cmd as cmd

class Shell(cmd.Cmd):
    def __init__(self, **kwargs):
        cmd.Cmd.__init__(self, **kwargs)

    def do_exit(self,*args):
        """
        Exits the shell gracefully
        :param args:
        :return:
        """
        print('Shutting down client...')
        return True

    def default(self, line):
        try:
            self._tcp_echo_client(line.encode())
        except SystemExit:
            pass

    def _tcp_echo_client(self, message):
        with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
            print('Send: %r' % message)
            s.connect(('127.0.0.1', 8888))
            s.sendall(message)
            data = s.recv(1000)
            print('Received: %r' % data.decode())
            print('Close the socket')

if __name__ == '__main__':
    Shell().cmdloop()

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-03-17
    • 2021-12-15
    • 2012-08-14
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多