我试图了解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()
我该如何改进/修复我的代码?
答案 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;)
您可能会认为,这很多代码都是关闭干净的,但据我了解,这是实现它的方法(或多或少是更大或更复杂的应用程序的唯一方法)。
祝你好运。