一种Pythonic方式,用于更改应从调用cancel()的位置取消异步任务的方式

时间:2019-02-14 12:38:07

标签: python python-asyncio

如何从取消任务的位置更改取消任务的行为?

我梦dream以求的东西

task = ensure_future(foo())

def foo_done(task)
    try:
        return task.get_result()
    except CancelError as e:
        when, why = e.args
        if when == "now"
            # do something...
        elif when == "asap":
            # do something else...
        else:
            # do default
        print(f"task cancelled because {why}")

task.add_done_callback(foo_done)

[...]

task.cancel("now", "This is an order!")

我可以在调用task.cancel()之前将对象附加到任务上,然后再检查它。

task = ensure_future(foo())

def foo_done(task)
    try:
        return task.get_result()
    except CancelError as e:
        when = getattr(task, "_when", "")
        why = getattr(task, "_why", "")
        if when == "now"
            # do something...
        elif when == "asap":
            # do something else...
        else:
            # do default
        print(f"task cancelled because {why}")

task.add_done_callback(foo_done)

[...]
task._when = "now"
task._why = "This is an order!"
task.cancel()

但是在某些情况下,例如当我想捕获正在处理的任务中的CancelError时,它显得笨拙:

async def foo():
   # some stuff
   try:
       # some other stuff
   except CancellError as e:
       # here I have easily access to the error, but not the task :(
   [...]

我正在寻找一种更Python化的方法。

1 个答案:

答案 0 :(得分:2)

您的解决方案用与您的异常相关的数据装饰Task实际上是一个很好的解决方案。在任务内,您可以访问正在使用asyncio.Task.current_task()处理的任务。

您还可以使用以下装饰器(未经测试)实现您梦dream以求的语法:

def propagate_when(fn):
    async def wrapped(*args, **kwds):
        try:
            return await fn(*args, **kwds)
        except CancelledError as e:
            e.when = getattr(asyncio.Task.current_task(), '_when', None)
            raise
    return wrapped

使用@propagate_when装饰协程可以在处理foo_done时访问e.when中的代码。缺点是CancelledError在任务中将不可用-在此您仍然必须使用e.when。由于存在这种不一致,我建议坚持从任务对象读取。

几个相关建议:

  • 将取消代码放入存储您传递的对象的实用程序函数中,然后调用current_task()。这种封装的薄层应该消除当前代码中的“笨拙”感觉。

  • 使用带前缀的属性名称-简短的通用名称(如task.cancel()会在将来的版本中引起冲突。 (我知道这只是一个例子,但未加前缀的名称始终有发生冲突的危险。)

  • 用单个对象装饰任务,将实际数据放入其属性。它使检索变得更简单,更干净,并且使您可以选择在存储的对象上实现方法。