中断事件循环后清理的正确方法是什么? [英] What's the correct way to clean up after an interrupted event loop?
问题描述
我有一个事件循环,它运行一些协同程序作为命令行工具的一部分.用户可以使用通常的 Ctrl + C 中断工具,此时我想在中断的事件循环后正确清理.
I have an event loop that runs some co-routines as part of a command line tool. The user may interrupt the tool with the usual Ctrl + C, at which point I want to clean up properly after the interrupted event loop.
这是我尝试过的.
import asyncio
@asyncio.coroutine
def shleepy_time(seconds):
print("Shleeping for {s} seconds...".format(s=seconds))
yield from asyncio.sleep(seconds)
if __name__ == '__main__':
loop = asyncio.get_event_loop()
# Side note: Apparently, async() will be deprecated in 3.4.4.
# See: https://docs.python.org/3.4/library/asyncio-task.html#asyncio.async
tasks = [
asyncio.async(shleepy_time(seconds=5)),
asyncio.async(shleepy_time(seconds=10))
]
try:
loop.run_until_complete(asyncio.gather(*tasks))
except KeyboardInterrupt as e:
print("Caught keyboard interrupt. Canceling tasks...")
# This doesn't seem to be the correct solution.
for t in tasks:
t.cancel()
finally:
loop.close()
运行并点击 Ctrl + C 产生:
Running this and hitting Ctrl + C yields:
$ python3 asyncio-keyboardinterrupt-example.py
Shleeping for 5 seconds...
Shleeping for 10 seconds...
^CCaught keyboard interrupt. Canceling tasks...
Task was destroyed but it is pending!
task: <Task pending coro=<shleepy_time() running at asyncio-keyboardinterrupt-example.py:7> wait_for=<Future cancelled> cb=[gather.<locals>._done_callback(1)() at /usr/local/Cellar/python3/3.4.3/Frameworks/Python.framework/Versions/3.4/lib/python3.4/asyncio/tasks.py:587]>
Task was destroyed but it is pending!
task: <Task pending coro=<shleepy_time() running at asyncio-keyboardinterrupt-example.py:7> wait_for=<Future cancelled> cb=[gather.<locals>._done_callback(0)() at /usr/local/Cellar/python3/3.4.3/Frameworks/Python.framework/Versions/3.4/lib/python3.4/asyncio/tasks.py:587]>
显然,我没有正确清理.我想也许在任务上调用 cancel()
会是这样做的方法.
Clearly, I didn't clean up correctly. I thought perhaps calling cancel()
on the tasks would be the way to do it.
在事件循环中断后进行清理的正确方法是什么?
What's the correct way to clean up after an interrupted event loop?
推荐答案
当您按 CTRL+C 时,事件循环停止,因此您对 t.cancel()
的调用实际上不会影响.对于要取消的任务,需要重新开始循环备份.
When you CTRL+C, the event loop gets stopped, so your calls to t.cancel()
don't actually take effect. For the tasks to be cancelled, you need to start the loop back up again.
以下是您可以处理它的方法:
Here's how you can handle it:
import asyncio
@asyncio.coroutine
def shleepy_time(seconds):
print("Shleeping for {s} seconds...".format(s=seconds))
yield from asyncio.sleep(seconds)
if __name__ == '__main__':
loop = asyncio.get_event_loop()
# Side note: Apparently, async() will be deprecated in 3.4.4.
# See: https://docs.python.org/3.4/library/asyncio-task.html#asyncio.async
tasks = asyncio.gather(
asyncio.async(shleepy_time(seconds=5)),
asyncio.async(shleepy_time(seconds=10))
)
try:
loop.run_until_complete(tasks)
except KeyboardInterrupt as e:
print("Caught keyboard interrupt. Canceling tasks...")
tasks.cancel()
loop.run_forever()
tasks.exception()
finally:
loop.close()
一旦我们捕捉到KeyboardInterrupt
,我们就会调用tasks.cancel()
,然后再次启动loop
.run_forever
实际上会在 tasks
被取消后立即退出(注意取消 asyncio.gather
返回的 Future
也取消其中的所有 Futures
),因为中断的 loop.run_until_complete
调用添加了一个 done_callback
到 tasks
停止循环.因此,当我们取消 tasks
时,会触发回调,然后循环停止.那时我们调用 tasks.exception
,只是为了避免收到关于未从 _GatheringFuture
获取异常的警告.
Once we catch KeyboardInterrupt
, we call tasks.cancel()
and then start the loop
up again. run_forever
will actually exit as soon as tasks
gets cancelled (note that cancelling the Future
returned by asyncio.gather
also cancels all the Futures
inside of it), because the interrupted loop.run_until_complete
call added a done_callback
to tasks
that stops the loop. So, when we cancel tasks
, that callback fires, and the loop stops. At that point we call tasks.exception
, just to avoid getting a warning about not fetching the exception from the _GatheringFuture
.
这篇关于中断事件循环后清理的正确方法是什么?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!