Python 3.4.2
我正在学习asyncio并且我用它来连续监听IPC总线,而gbulb则监听dbus。
所以我创建了一个函数listen_to_ipc_channel_layer
,它不断监听IPC频道上的传入消息,并将消息传递给message_handler
。
我也在听SIGTERM和SIGINT。因此,当我向运行您在底部找到的代码的python进程发送SIGTERM时,脚本应该正常终止。
......我有以下警告:
got signal 15: exit
Task was destroyed but it is pending!
task: <Task pending coro=<listen_to_ipc_channel_layer() running at /opt/mainloop-test.py:23> wait_for=<Future cancelled>>
Process finished with exit code 0
...使用以下代码:
import asyncio
import gbulb
import signal
import asgi_ipc as asgi
def main():
asyncio.async(listen_to_ipc_channel_layer())
loop = asyncio.get_event_loop()
for sig in (signal.SIGINT, signal.SIGTERM):
loop.add_signal_handler(sig, ask_exit)
# Start listening on the Linux IPC bus for incoming messages
loop.run_forever()
loop.close()
@asyncio.coroutine
def listen_to_ipc_channel_layer():
"""Listens to the Linux IPC bus for messages"""
while True:
message_handler(message=channel_layer.receive(["my_channel"]))
try:
yield from asyncio.sleep(0.1)
except asyncio.CancelledError:
break
def ask_exit():
loop = asyncio.get_event_loop()
for task in asyncio.Task.all_tasks():
task.cancel()
loop.stop()
if __name__ == "__main__":
gbulb.install()
# Connect to the IPC bus
channel_layer = asgi.IPCChannelLayer(prefix="my_channel")
main()
我仍然只了解asyncio,但我想我知道发生了什么。在等待yield from asyncio.sleep(0.1)
信号处理程序捕获SIGTERM时,在该过程中它调用task.cancel()
。
提出的问题:不应该触发CancelledError
循环中的while True:
吗?(因为它不是,但这就是我的理解{{ 3}})。
最终会调用loop.stop()
来停止循环而不等待yield from asyncio.sleep(0.1)
返回结果甚至整个协程listen_to_ipc_channel_layer
。
如果我错了,请纠正我。
我认为我唯一需要做的就是让我的程序等待yield from asyncio.sleep(0.1)
返回结果和/或 coroutine来打破while循环并完成。
我相信我混淆了很多事情。请帮我把这些东西直接搞定,这样我就可以弄清楚如何在没有警告的情况下优雅地关闭事件循环。
答案 0 :(得分:12)
问题来自于取消任务后立即关闭循环。作为cancel() docs state
&#34;这将安排CancelledError通过事件循环投放到下一个周期的包装协程中。&#34;
获取这段代码:
import asyncio
import signal
async def pending_doom():
await asyncio.sleep(2)
print(">> Cancelling tasks now")
for task in asyncio.Task.all_tasks():
task.cancel()
print(">> Done cancelling tasks")
asyncio.get_event_loop().stop()
def ask_exit():
for task in asyncio.Task.all_tasks():
task.cancel()
async def looping_coro():
print("Executing coroutine")
while True:
try:
await asyncio.sleep(0.25)
except asyncio.CancelledError:
print("Got CancelledError")
break
print("Done waiting")
print("Done executing coroutine")
asyncio.get_event_loop().stop()
def main():
asyncio.async(pending_doom())
asyncio.async(looping_coro())
loop = asyncio.get_event_loop()
for sig in (signal.SIGINT, signal.SIGTERM):
loop.add_signal_handler(sig, ask_exit)
loop.run_forever()
# I had to manually remove the handlers to
# avoid an exception on BaseEventLoop.__del__
for sig in (signal.SIGINT, signal.SIGTERM):
loop.remove_signal_handler(sig)
if __name__ == '__main__':
main()
注意ask_exit
取消任务,但不会stop
循环,在下一个周期looping_coro()
停止它。取消输出的是:
Executing coroutine
Done waiting
Done waiting
Done waiting
Done waiting
^CGot CancelledError
Done executing coroutine
注意pending_doom
如何在之后立即取消并停止循环。如果你让它运行直到pending_doom
协同程序从睡眠中醒来,你可以看到同样的警告:
Executing coroutine
Done waiting
Done waiting
Done waiting
Done waiting
Done waiting
Done waiting
Done waiting
>> Cancelling tasks now
>> Done cancelling tasks
Task was destroyed but it is pending!
task: <Task pending coro=<looping_coro() running at canceling_coroutines.py:24> wait_for=<Future cancelled>>
答案 1 :(得分:4)
问题的含义是循环没有时间完成所有任务。
这会安排在事件循环的下一个循环中将CancelledError抛出到包装的协同程序中。
在你的方法中没有机会进行循环的“下一个循环”。为了使它正确,你应该将一个停止操作移动到一个单独的非循环协程,让你的循环有机会完成。
第二个重要的事情是CancelledError
提升。
与Future.cancel()不同,这并不能保证任务将被取消:异常可能被捕获并采取行动,延迟取消任务或完全阻止取消。任务还可以返回值或引发不同的异常。
调用此方法后,cancel()不会返回True(除非任务已被取消)。当包装的协同程序以CancelledError异常终止时(即使未调用cancel()),任务将被标记为已取消。
因此,清理后你的协程必须提升CancelledError
才能被标记为已取消。
使用额外的协程停止循环不是问题,因为它不是循环的,并且在执行后立即完成。
def main():
loop = asyncio.get_event_loop()
asyncio.ensure_future(listen_to_ipc_channel_layer())
for sig in (signal.SIGINT, signal.SIGTERM):
loop.add_signal_handler(sig, ask_exit)
loop.run_forever()
print("Close")
loop.close()
@asyncio.coroutine
def listen_to_ipc_channel_layer():
while True:
try:
print("Running")
yield from asyncio.sleep(0.1)
except asyncio.CancelledError as e:
print("Break it out")
raise e # Raise a proper error
# Stop the loop concurrently
@asyncio.coroutine
def exit():
loop = asyncio.get_event_loop()
print("Stop")
loop.stop()
def ask_exit():
for task in asyncio.Task.all_tasks():
task.cancel()
asyncio.ensure_future(exit())
if __name__ == "__main__":
main()
答案 2 :(得分:2)
发生这种情况的原因由@Yeray Diaz Diaz解释
就我而言,我想取消所有在第一个完成后未完成的任务,因此我最终取消了多余的作业,然后使用loop._run_once()
再次运行循环并允许它们停止: / p>
loop = asyncio.get_event_loop()
job = asyncio.wait(tasks, return_when=asyncio.FIRST_COMPLETED)
tasks_finished,tasks_pending, = loop.run_until_complete(job)
tasks_done = [t for t in tasks_finished if t.exception() is None]
if tasks_done == 0:
raise Exception("Failed for all tasks.")
assert len(tasks_done) == 1
data = tasks_done[0].result()
for t in tasks_pending:
t.cancel()
t.cancel()
while not all([t.done() for t in tasks_pending]):
loop._run_once()
答案 3 :(得分:0)
我收到了此消息,我相信这是由于未完成任务的垃圾收集引起的。 Python开发人员正在争论在asyncio中创建的任务是否应该创建强引用,并决定不应该这样做(在研究了此问题2天后,我强烈不同意!...请参见此处的讨论https://bugs.python.org/issue21163)
我为自己创建了该实用程序,以对任务进行强大引用并自动将其清除(仍然需要对其进行彻底测试)...
import asyncio
#create a strong reference to tasks since asyncio doesn't do this for you
task_references = set()
def register_ensure_future(coro):
task = asyncio.ensure_future(coro)
task_references.add(task)
# Setup cleanup of strong reference on task completion...
def _on_completion(f):
task_references.remove(f)
task.add_done_callback(_on_completion)
return task
在我看来,只要任务处于活动状态,它们就应该具有强烈的参考意义!但是asyncio不会为您做到这一点,因此一旦发生gc并需要长时间的调试,您可能会感到有些意外。