Python 请解释“任务已销毁但待处理!”
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/40897428/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Please explain "Task was destroyed but it is pending!"
提问by Daniel
Python 3.4.2
蟒蛇 3.4.2
I am learning asyncio and I use it to continously listen IPC bus, while gbulb listens to the dbus.
我正在学习 asyncio,我用它来持续监听 IPC 总线,而 gbulb 监听 dbus。
Some side notes:
一些旁注:
So I created a function listen_to_ipc_channel_layer
that continously listens for incoming messages on the IPC channel and passes the message to a message_handler
.
所以我创建了一个函数listen_to_ipc_channel_layer
,它持续监听 IPC 通道上的传入消息并将消息传递给message_handler
.
I am also listening to SIGTERM and SIGINT. So when I send a SIGTERM to the python process running the code you find at the bottom, the script should terminate gracefully.
我也在听 SIGTERM 和 SIGINT。因此,当我向运行您在底部找到的代码的 python 进程发送 SIGTERM 时,脚本应该正常终止。
The problem
问题
… I am having is the following warning:
...我有以下警告:
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
… with the following code:
...使用以下代码:
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()
I still only understand very little of asyncio, but I think I know what is going on. While waiting for yield from asyncio.sleep(0.1)
the signal handler caught the SIGTERM and in that process it calls task.cancel()
.
我仍然对 asyncio 了解得很少,但我想我知道发生了什么。在等待yield from asyncio.sleep(0.1)
信号处理程序时捕获 SIGTERM 并在该过程中调用task.cancel()
.
Question thrown in: Shouldn't this trigger the CancelledError
within the while True:
loop?(Because it is not, but that is how I understand "Calling cancel() will throw a CancelledError to the wrapped coroutine").
抛出的问题:这不应该触发循环CancelledError
内的while True:
吗?(因为它不是,但这就是我的理解“调用 cancel() 将向包装的协程抛出 CancelledError”)。
Eventually loop.stop()
is called which stops the loop without waiting for either yield from asyncio.sleep(0.1)
to return a result or even the whole coroutine listen_to_ipc_channel_layer
.
最终loop.stop()
被调用,它停止循环而不等待yield from asyncio.sleep(0.1)
返回结果甚至整个协程listen_to_ipc_channel_layer
。
Please correct me if I am wrong.
如果我错了,请纠正我。
I think the only thing I need to do is to make my program wait for the yield from asyncio.sleep(0.1)
to return a result and/orcoroutine to break out the while-loop and finish.
我认为我唯一需要做的就是让我的程序等待yield from asyncio.sleep(0.1)
返回结果和/或协程以打破 while 循环并完成。
I believe I confuse a lot of things. Please help me get those things straight so that I can figure out how to gracefully close the event loop without warning.
我相信我混淆了很多东西。请帮助我弄清楚这些事情,以便我可以弄清楚如何在没有警告的情况下优雅地关闭事件循环。
采纳答案by Yeray Diaz Diaz
The problem comes from closing the loop immediately after cancelling the tasks. As the cancel() docs state
问题来自取消任务后立即关闭循环。正如取消()文档状态
"This arranges for a CancelledError to be thrown into the wrapped coroutine on the next cyclethrough the event loop."
“这安排了 CancelledError在事件循环的下一个循环中被抛出到包装的协程中。”
Take this snippet of code:
拿这段代码:
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()
Notice ask_exit
cancels the tasks but does not stop
the loop, on the next cycle looping_coro()
stops it. The output if you cancel it is:
通知ask_exit
取消任务但不stop
循环,在下一个循环中looping_coro()
停止它。如果取消它,输出是:
Executing coroutine
Done waiting
Done waiting
Done waiting
Done waiting
^CGot CancelledError
Done executing coroutine
Notice how pending_doom
cancels and stops the loop immediately after. If you let it run until the pending_doom
coroutines awakes from the sleep you can see the same warning you're getting:
注意在 之后如何立即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>>
回答by I159
The meaning of the issue is that a loop doesn't have time to finish all the tasks.
问题的含义是循环没有时间完成所有任务。
This arranges for a CancelledError to be thrown into the wrapped coroutine on the next cycle through the event loop.
这安排在事件循环的下一个循环中将 CancelledError 抛出到包装的协程中。
There is no chance to do a "next cycle" of the loop in your approach. To make it properly you should move a stop operation to a separate non-cyclic coroutine to give your loop a chance to finish.
在您的方法中没有机会执行循环的“下一个循环”。为了使它正确,您应该将停止操作移动到一个单独的非循环协程,让您的循环有机会完成。
Second significant thing is CancelledError
raising.
第二个重要的事情是CancelledError
提高。
Unlike Future.cancel(), this does not guarantee that the task will be cancelled: the exception might be caught and acted upon, delaying cancellation of the task or preventing cancellation completely. The task may also return a value or raise a different exception.
Immediately after this method is called, cancelled() will not return True (unless the task was already cancelled). A task will be marked as cancelled when the wrapped coroutine terminates with a CancelledError exception (even if cancel() was not called).
与 Future.cancel() 不同,这并不能保证任务会被取消:异常可能会被捕获并采取行动,延迟取消任务或完全阻止取消。任务也可能返回一个值或引发不同的异常。
调用此方法后,cancelled() 不会立即返回 True(除非任务已被取消)。当包装的协程因 CancelledError 异常终止时(即使没有调用 cancel()),任务将被标记为已取消。
So after cleanup your coroutine must raise CancelledError
to be marked as cancelled.
因此,在清理之后,您的协程必须提升CancelledError
以标记为已取消。
Using an extra coroutine to stop the loop is not an issue because it is not cyclic and be done immediately after execution.
使用额外的协程来停止循环不是问题,因为它不是循环的并且在执行后立即完成。
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()