管理后台异步任务
aionurser的Python项目详细描述
这个库实现了一个托儿所对象,类似于trio的Nurseryfor asyncio。
asyncdefchild():...asyncdefparent():asyncwithaionursery.Nursery()asnursery:# Make two concurrent calls to childnursery.start_soon(child())nursery.start_soon(child())
任务形成一棵树:当您运行主协同程序(通过asyncio.get_event_loop().run_until_complete或asyncio.run)时,这将创建一个初始任务,并且所有其他任务都将是主任务的子任务、孙子任务等。
async with块的主体类似于在托儿所内运行的初始任务,然后对nursery.start_soon的每次调用都会添加另一个并行运行的任务。
请记住:
- 如果托儿所内的任何任务引发未处理的异常,则托儿所立即取消托儿所内的所有任务。
- 由于所有任务都在块中异步运行,所以在所有任务完成之前,块不会退出。如果您使用过其他并发框架,那么可以将其视为异步结束时的取消缩进,并自动“连接”(等待)托儿所中的所有任务。
- 所有任务完成后,然后: *托儿所被标记为“关闭”,这意味着不能在其中启动新任务。 *所有未处理的异常都会在父任务内部重新引发。如果存在多个异常,则它们将被收集到单个多错误异常中。
由于所有任务都是初始任务的子代,因此这会导致父任务在所有任务完成之前无法完成。
请注意,不能重用已退出的托儿所。再次尝试重新打开它,或在其中start_soon更多任务将引发NurseryClosed。
阻止取消某些任务
然而,有时需要有相反的行为:无论在其他任务中引发了什么异常,子任务都必须执行。 想象一个支付交易在一个任务中运行,而一个短信在另一个任务中发送。 您当然不希望短信发送错误取消支付交易。
为此,您可以asyncio.shield在托儿所开始您的任务:
asyncdefperform_payment():...asyncdefsend_sms():...asyncdefparent():asyncwithNursery()asnursery:nursery.start_soon(asyncio.shield(perform_payment()))nursery.start_soon(send_sms())
从孩子那里得到结果
如果您的后台任务不是很长,并且返回一些您想要处理的有用值,那么您可以将所有任务收集到一个列表中,并像往常一样使用asyncio.wait(或类似的函数):
asyncdefparent():asyncwithNursery()asnursery:task_foo=nursery.start_soon(foo())task_bar=nursery.start_soon(bar())results=awaitasyncio.wait([task_foo,task_bar])
如果后台任务是长寿命的,则应使用asyncio.Queue在子任务和父任务之间传递对象:
asyncdefchild(queue):whileTrue:data=awaitfrom_external_system()awaitqueue.put(data)asyncdefparent():queue=asyncio.Queue()asyncwithNursery()asnursery:nursery.start_soon(child(queue))whilesome_condition():data=awaitqueue.get()awaitdo_stuff_with(data)
与async_timeout
集成您可以在async_timeout.timeout上下文管理器中包装托儿所。 超时时,整个托儿所取消:
fromasync_timeoutimporttimeoutasyncdefchild():awaitasyncio.sleep(1000*1000)asyncdefparent():asyncwithtimeout(10):asyncwithNursery()asnursery:nursery.start_soon(child())awaitasyncio.sleep(1000*1000)