简而言之,您所有问题的答案都是“否”。
在 asyncio 中没有父任务与子任务这样的概念,也没有任务之间的任何层次关系。只有一个“级别”——所有任务都是等效的。
在某种程度上,您可以通过在 finally 块中显式取消任务来强制依赖,例如,
async def f():
t1 = asyncio.create_task(coroutine1())
t2 = asyncio.create_task(coroutine2())
try:
r1 = await t1
r3 = await process_result(r1) # process_result throws an exception
r2 = await t2
return await process_results(r2, r3)
finally:
t1.cancel()
t2.cancel()
但这不会取消任何恰好由 t1 和 t2 创建的任务。它只会创建一个级别的依赖关系。
在最近的几个小项目中,我成功地使用这个类将任务组织成层次结构:
import asyncio
import logging
logger = logging.getLogger(__name__)
class BagContextError(Exception):
pass
class PBag:
def __init__(self):
self.futures = set()
self.exceptions = []
self.done = asyncio.Event()
self.done.set()
self._opened = False
self._closed = False
@property
def is_open(self):
return self._opened and not self._closed
def __await__(self):
yield from self.done.wait().__await__()
async def __aenter__(self):
if self._opened:
raise BagContextError("Already open")
self._opened = True
return self
async def __aexit__(self, exc_type, _exc_value, _traceback):
logger.debug("Bag exit %s %s %s", self.futures, self.exceptions,
exc_type, stack_info=True)
self._closed = True
await self.aclose()
if self.exceptions:
n = 1 if exc_type is None else 0
for ex in self.exceptions[n:]:
try:
raise ex
except Exception:
logging.exception("Suppressed exception")
if exc_type is None:
raise self.exceptions[0]
def until_done(self):
return self.done.wait()
def create_task(self, coro):
if self._closed:
raise BagContextError("Bag closed")
t = asyncio.create_task(coro)
self.add_future(t)
return t
def add_future(self, fut):
if self._closed:
raise BagContextError("Bag closed")
self.futures.add(fut)
fut.add_done_callback(self._future_done)
self.done.clear()
def close(self):
for w in self.futures:
w.cancel()
async def aclose(self):
self.close()
await self.until_done()
def _future_done(self, fut):
try:
self.futures.remove(fut)
except KeyError:
pass
if not self.futures:
self.done.set()
try:
fut.result()
except asyncio.CancelledError:
pass
except Exception as ex:
self.exceptions.append(ex)
它是一个上下文管理器。在其上下文中,任务由 PBag.create_task 而不是 asyncio.create_task 创建。该对象跟踪其依赖的任务,并在发生异常、退出上下文或调用 close() 方法时关闭它们。
如果你用它来构建一个完整的程序,任务将被分层排列,当最外面的任务被取消时,整个结构将(有点)优雅地展开。如果您只在某些地方使用它而不在其他地方使用它(即,如果您在某些地方编写 asyncio.create_task),则此展开可能效果不佳。
我对它没有太多经验,所以当然可能存在未被发现的错误。这是一个小演示程序:
async def main():
async def task1():
print("Task1 started", time.ctime())
await asyncio.sleep(2)
print("Task1 finished", time.ctime())
async def task2():
print("Task2 started", time.ctime())
await asyncio.sleep(3)
raise Exception("Task 2 error")
async def task3(bag):
bag.create_task(task2())
print("Task 3 done")
try:
async with PBag() as bag:
bag.create_task(task1())
bag.create_task(task3(bag))
await bag.until_done()
bag.create_task(task1())
await bag
except asyncio.CancelledError:
traceback.print_exc()
except Exception:
traceback.print_exc()
print("Bag closed", time.ctime())
asyncio.create_task(task1())
print("Program finished", time.ctime())
if __name__ == "__main__":
asyncio.run(main())