You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

asyncio疑问:取消任务为何导致事件循环中其他任务被取消?

问题描述

我用runTimes协程多次将slowPrinter协程加入事件循环,但中途取消runTimes任务后,不仅不再添加新任务,就连已加入事件循环的任务似乎也被取消。我在Spyder环境运行脚本,无需调用run_until_complete等方法,因为事件循环已在后台运行。

我确认代码按预期执行,但无法理解原因,同时想知道如何在取消runTimes的前提下,让slowPrinter任务仍能完成。

原始代码

import asyncio

loop = asyncio.get_event_loop()

async def runTimes(async_func, times):
    for i in range(0, times):
        task = loop.create_task(async_func())
        await task
        
async def slowPrinter():
    await asyncio.sleep(2)
    print("slowPrinter done")
    

async def doStuff():
    for i in range(0, 10):
        await(asyncio.sleep(1))
    print("doStuff done")
        
async def doLater(delay_ms, method, *args, **kwargs):
    try:
        print("doLater " + str(delay_ms) + " " + str(method.__name__))
    except AttributeError:
        print("doLater " + str(delay_ms))
    await asyncio.sleep(delay_ms/1000)
    method(*args, **kwargs)
    print("doLater complete")
        
task = loop.create_task(runTimes(slowPrinter, 3))
loop.create_task(doLater(3000, task.cancel))
loop.create_task(doStuff())

实际输出

doLater 3000 cancel
slowPrinter done
doLater complete
doStuff done

预期输出

doLater 3000 cancel
slowPrinter done
doLater complete
slowPrinter done
doStuff done

编辑说明

我未使用run_later等方法,因后续要将代码移植到MicroPython,仅使用兼容函数。

编辑补充

任务取消似乎会传播到协程内部创建的任务!

修改后的runTimes代码:

async def runTimes(async_func, times):
    for i in range(0, times):
        task = loop.create_task(async_func())
        try:
            await task
        except asyncio.CancelledError:
            print("cancelled as well")

对应输出:

doLater 3000 cancel
slowPrinter done
doLater complete
cancelled as well
slowPrinter done
doStuff done

原因分析与解决方案

核心原因

你看到的“已添加任务被取消”是误解,实际时间线如下:

  1. 程序启动后,runTimes立即创建第一个slowPrinter任务并等待它完成(耗时2秒)。
  2. 3秒后doLater触发runTimes任务的取消操作,此时第一个slowPrinter已经执行完毕。
  3. 取消信号会在runTimes的await语句处抛出CancelledError,如果没捕获这个异常,runTimes会直接终止,根本没机会进入下一次循环创建第二个slowPrinter任务。

你补充的代码中捕获了CancelledError,所以runTimes在触发取消后没有直接终止,而是继续执行循环,创建了第二个slowPrinter,因此输出里多了对应的slowPrinter done。

另外要明确:用loop.create_task创建的任务是独立于父任务的,父任务被取消不会自动取消子任务,子任务会继续在事件循环中执行。

解决方案

要实现“取消runTimes后,已创建的slowPrinter任务完成,且未创建的任务也能被执行”(或者至少已启动的任务不受影响),可以按以下方式处理:

方案1:捕获CancelledError,让runTimes完成剩余循环

修改runTimes,在循环外层捕获取消异常,确保即使自身被取消,已创建的任务继续执行,且能完成剩余的任务创建:

async def runTimes(async_func, times):
    try:
        for i in range(times):
            task = loop.create_task(async_func())
            await task
    except asyncio.CancelledError:
        print("runTimes 被取消,但已创建的任务会继续执行")
        # 如果需要,可以在这里处理未完成的任务逻辑

方案2:不等待子任务,直接批量创建所有任务

如果你不需要等待每个slowPrinter完成后再创建下一个,而是一次性把所有任务都加入事件循环,那么即使runTimes被取消,所有slowPrinter任务都已经存在于事件循环中,会自行执行完毕:

async def runTimes(async_func, times):
    tasks = []
    for i in range(times):
        task = loop.create_task(async_func())
        tasks.append(task)
    await asyncio.gather(*tasks)

这种情况下,即使runTimes被取消,已经创建的tasks仍然会在事件循环中执行,直到完成。

关键结论

  • loop.create_task创建的任务是独立的,父任务的取消不会传递给子任务。
  • 你之前的问题本质是runTimes被取消后,没有机会创建后续任务,而非已创建任务被取消。
  • 捕获CancelledError是让runTimes继续执行或优雅退出的关键。

内容的提问来源于stack exchange,提问作者Vitalize0769

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.12 05:42:07