所有任务都在等待时立即取消python asyncio任务

时间:2020-04-15 00:44:08

标签: python python-3.x python-asyncio

似乎python事件循环将不执行任何操作,直到内部完成某些工作为止。这意味着,取消任务实际上不会做任何事情,直到其中一个任务醒来并开始执行。

SSCCE:

import asyncio
import signal
import time

start_time = time.time()
task1 = None
task2 = None


async def wait(name, duration):
    try:
        await asyncio.sleep(duration)
    except asyncio.CancelledError:
        print(f"{name} got cancellation ({(time.time() - start_time):.2})")
        pass


async def main():
    global task1
    global task2
    task1 = asyncio.ensure_future(wait("task1", 5))
    task2 = asyncio.ensure_future(wait("taks2", 10))

    await asyncio.gather(task1, task2)


def run():
    def handle_signal(a, b):
        print(f"cancelling main task ({(time.time() - start_time):.2})")
        task1.cancel()
        task2.cancel()
    signal.signal(signal.SIGINT, handle_signal)

    asyncio.run(main())
    print('done')


if __name__ == "__main__":
    run()

输出:

$ python3.7 async.py 
^Ccancelling main task (0.88)
task1 got cancellation (5.0)
taks2 got cancellation (5.0)
done

如何强制事件循环立即取消这些任务,而不必等待一个任务从睡眠中醒来?

使用python3.6和python3.7

1 个答案:

答案 0 :(得分:1)

我将这个答案的旧内容保留下来,以演示在使用信号处理程序时可以使用的技术,但事实证明asyncio实际上具有自己的信号处理API。

loop.add_signal_handler可用于设置异步兼容的信号处理程序。它们将在事件循环的控制下被调用,并且与常规信号处理程序不同,它们可以与事件循环安全地进行交互。 (请注意,这意味着事件循环必须等待任何当前正在执行的协程来产生控制权,然后循环才能运行信号处理程序。)与常规信号处理程序不同,通过此API设置的信号处理程序不应使用任何参数。

如果您从信号处理程序的定义中删除参数,并使用abstract class DelegatingObjectSet<R> implements ObjectSet<R> { // you can use dependency injection here private final ObjectSet<R> delegate = new ObjectSetImpl<>(); @Override public <V> V getValue(Input<R, V> input) { return delegate.getValue(input); } } 设置处理程序,那么它将正常工作。实际上,最好在创建任务后从asyncio.get_event_loop().add_signal_handler(signal.SIGINT, handle_signal)设置处理程序,然后使用main

asyncio.get_running_loop()

下面是一个古老的答案,它通过普通的信号处理API而不是asyncio的API来完成工作,因为我在编写asyncio时不知道asyncio的信号。


这里有两个主要问题。

问题1是尝试直接从信号处理程序中取消任务已经是不安全的。信号处理程序可以在def handle_signal(): print(f"cancelling main task ({(time.time() - start_time):.2})") task1.cancel() task2.cancel() async def main(): global task1 global task2 task1 = asyncio.ensure_future(wait("task1", 5)) task2 = asyncio.ensure_future(wait("taks2", 10)) asyncio.get_running_loop().add_signal_handler(signal.SIGINT, handle_signal) await asyncio.gather(task1, task2) 进行内部工作并且其数据结构处于不一致状态时运行。这种问题甚至比并发问题更严重,因为锁定无济于事。在信号处理程序中,您几乎可以安全地做些什么。

问题2是asyncio安排在事件循环的下一次迭代中将task.cancel()放入包装的协程 中。您需要一些东西来触发事件循环的迭代。


您可以在Python信号处理程序中执行的最复杂,最有用的操作之一就是通过queue.SimpleQueue发送消息。这是一个同步队列,它以有限的功能进行交换,以便能够在不稳定的条件下安全地调用其CancelledError方法,例如put方法,weakref回调以及可能在同一线程中中断复杂操作的其他代码。 / p>

__del__不兼容异步,因此接收消息的代码必须在另一个线程中。该线程也不能安全地调用queue.SimpleQueue.get,因为asyncio中只有非常有限的部分被设计为线程安全的,而cancel也不是记录为线程安全的部分之一。实际上,Task.cancel文档的第二句话是“不是线程安全的”。

但是,您可以让任务使用asyncio.Task在线程中运行一个函数,并使该函数从队列中接收取消消息。该任务可以等待该函数完成,然后从事件循环内部安全地执行取消操作。取消任务完成后,事件循环的另一次迭代将立即运行,您要取消的任务将被取消。

loop.run_in_executor