在asyncio中的KeyboardInterrupt之后等待任务完成

时间:2016-02-24 13:50:46

标签: python python-3.4 python-asyncio

我试图了解asyncio的工作原理。在我的场景中,客户端与服务器建立tcp连接,如果经过身份验证,则发送登录字符串 - 接收字符流。最后在KeyboardInterrupt上向服务器发送logoff字符串,然后断开连接。

目前我已经停留在最后一部分,因为我的注销方法/任务在有机会完成之前就被销毁了。

^CTask was destroyed but it is pending!
source_traceback: Object created at (most recent call last):
  File "tst.py", line 101, in <module>
    client.login()
  File "tst.py", line 29, in login
    logoff_tsk = self.loop.create_task(self.logoff())
task: <Task pending coro=<logoff() running at tst.py:49> cb=[myClass._shutdown()] created at tst.py:29>

以下是产生此错误的代码:

from functools import partial
import asyncio

class myClass:
    def __init__(self, *a, **kw):
        self.transport = None
        self.protocol = None
        self.usr = str(kw.get("usr", ""))
        self.pwd = str(kw.get("pwd", ""))
        self.host = str(kw.get("host", "")) or "127.0.0.2"
        self.port = int(kw.get("port", 0)) or 5038
        self.loop = asyncio.get_event_loop()
        self.loop.set_debug(enabled=True)

    def reactor(self, recv):
        print("## ~/~ From reactor: {!r}".format(recv.decode()))

    def login(self):
        connection_tsk = self.loop.create_task(self.loop.create_connection(
            partial(
                myProtocol,
                reactor_func=self.reactor),
            host=self.host,
            port=self.port))
        connection_tsk.add_done_callback(self.set_transport_protocol)
        try:
            self.loop.run_forever()
        except KeyboardInterrupt:
            logoff_tsk = self.loop.create_task(self.logoff())
            logoff_tsk.add_done_callback(self._shutdown)

    def set_transport_protocol(self, fut):
        print("AmiCtl.set_transport_protocol")
        transport, protocol = fut.result()
        self.transport = transport
        self.protocol = protocol
        self.loop.call_soon(self._login)

    def _login(self):
        login_string = self.cmd("Login")
        self.loop.create_task(self.transmit(login_string))

    @asyncio.coroutine
    def transmit(self, cmd):
        if self.transport:
            print("## ~/~ Sending data: {!r}".format(cmd))
            self.transport.write(cmd)

    @asyncio.coroutine
    def logoff(self):
        command = self.cmd("Logoff")
        yield from asyncio.shield(self.transmit(command))

    def _shutdown(self):
        if self.transport:
            self.transport.close()
        self.loop.stop()
        self.loop.close()
        print("\n{!r}".format(self.loop))

    def cmd(self, action):
        """
        Produce login/logoff string.
        """

class myProtocol(asyncio.Protocol):
    def __init__(self, reactor_func=None):
        self.reactor_func = reactor_func
        self.loop = asyncio.get_event_loop()

    def connection_made(self, transport):
        self.transport = transport
        peername = transport.get_extra_info("peername")
        print("## ~/~ Connection made: {peer}".format(peer=peername))

    def data_received(self, data):
        if callable(self.reactor_func):
            self.reactor_func(data)

    def connection_lost(self, exc):
        print("## ~/~ Lost connection to the server!!")
        self.loop.stop()


client = myClass(usr="my_usr", pwd="my_pwd")
client.login()

我该如何改进/修复我的代码?

1 个答案:

答案 0 :(得分:3)

我不会参考你的代码,因为我认为你的问题是一个xy问题。 (http://xyproblem.info),但我会尝试以更通用的方式回答。我正在阅读的是您正在询问如何正确关闭asyncio应用程序。

我已经设置了一个小例子,我认为通过一些解释会让你走上正轨。阅读下面的代码,我希望它可以与您的用例相关,我将在下面解释。

import asyncio
import signal

loop = asyncio.get_event_loop()


class Connector:

    def __init__(self):
        self.closing = False
        self.closed = asyncio.Future()
        task = loop.create_task(self.connection_with_client())
        task.add_done_callback(self.closed.set_result)


    async def connection_with_client(self):
        while not self.closing:
            print('Read/write to open connection')
            await asyncio.sleep(1)

        print('I will now close connection')
        await asyncio.sleep(1)


conn = Connector()


def stop(loop):
    conn.closing = True
    print("from here I will wait until connection_with_client has finished")
    conn.closed.add_done_callback(lambda _: loop.stop())

loop.add_signal_handler(signal.SIGINT, stop, loop)
loop.run_forever()
loop.close()

您所要求的实际上并非无足轻重,最难管理的事情之一就是正确关闭asyncio应用程序。

要进行托管关闭,你必须有一个协同程序,它将处理关闭并设置af将来完成,当它确保一切都关闭时。在我的例子中它是task.add_done_callback(self.closed.set_result)但是这个未来可以用其他方式设置。

其次,你必须添加一个信号处理程序,它将运行一个非异步(正常)函数并安排一个回调,当你的关闭未来时,它会触发循环关闭/停止。完了。

看看我的代码和玩具,直到你理解了流程。我不会责怪你的问题,在我看来,做asyncio最困难的事情之一是跟踪“松散”的情况。协同程序,这将导致不洁的关闭。

当我做这个&#39;运动&#39;我的第一次,需要了解我通过https://github.com/aio-libs/aioredis/blob/master/aioredis/connection.py#L93-L95代码的原则 如果你花时间阅读代码这些人以非常漂亮的方式处理你的确切问题,我知道代码是一个但很复杂,但是拿一杯咖啡并按照方法调用,直到它有意义。

如果我不清楚,我希望这可以帮助你,发布更多细节或评论。并花时间了解这个主题(asycnio关闭的东西)。当您使用asyncio掌握关机管理时,您不再是asyncio-padawan;)

您可能会认为,这很多代码都是关闭干净的,但据我了解,这是实现它的方法(或多或少是更大或更复杂的应用程序的唯一方法)。

祝你好运。