MST

星途 面试题库

面试题:Kotlin协程取消与异常传播

假设你有一个由多个子协程组成的父协程,在父协程取消时,子协程的异常会如何传播?如何确保异常被正确捕获并且资源得到合理释放?请详细描述实现思路和关键代码结构。
20.4万 热度难度
编程语言Kotlin

知识考点

AI 面试

面试题答案

一键面试

异常传播机制

  1. 默认行为:在Python的asyncio库中,当父协程被取消时,子协程如果没有处理CancelledError,它们会被取消,并且CancelledError会在子协程中被抛出。如果子协程在处理CancelledError的过程中又抛出了其他异常,这些异常默认不会向上传播到父协程。
  2. 捕获和处理异常:为了让子协程的异常能够传播到父协程,子协程需要捕获CancelledError,并在处理时将其他异常重新抛出。

实现思路

  1. 子协程
    • 捕获CancelledError
    • 在处理CancelledError时,如果有其他异常,将其重新抛出。
    • 使用try - finally块来确保资源在子协程结束时被释放。
  2. 父协程
    • 捕获子协程抛出的异常。
    • 使用try - finally块来确保父协程取消时资源的释放。

关键代码结构

以下是Python asyncio库中的示例代码:

import asyncio


async def child_coroutine():
    try:
        # 模拟子协程的工作
        await asyncio.sleep(1)
        raise ValueError("子协程出现异常")
    except asyncio.CancelledError:
        # 处理取消异常
        print("子协程被取消")
        # 如果还有其他异常,重新抛出
        raise
    finally:
        # 释放资源,如关闭文件、数据库连接等
        print("子协程资源释放")


async def parent_coroutine():
    tasks = [asyncio.create_task(child_coroutine()) for _ in range(3)]
    try:
        await asyncio.gather(*tasks)
    except ValueError as ve:
        # 捕获子协程抛出的异常
        print(f"捕获到子协程异常: {ve}")
    except asyncio.CancelledError:
        # 处理父协程取消异常
        print("父协程被取消")
        for task in tasks:
            task.cancel()
        await asyncio.gather(*tasks, return_exceptions=True)
    finally:
        # 释放资源,如关闭文件、数据库连接等
        print("父协程资源释放")


if __name__ == "__main__":
    loop = asyncio.get_event_loop()
    try:
        loop.run_until_complete(parent_coroutine())
    except KeyboardInterrupt:
        print("手动取消")
        tasks = asyncio.all_tasks(loop)
        for task in tasks:
            task.cancel()
        loop.run_until_complete(asyncio.gather(*tasks, return_exceptions=True))
    finally:
        loop.close()
  1. child_coroutine
    • try块:包含子协程的业务逻辑,这里模拟了一个睡眠操作并抛出ValueError
    • except asyncio.CancelledError块:处理子协程被取消的情况,并重新抛出其他异常。
    • finally块:确保无论子协程是正常结束、被取消还是抛出异常,资源都能被释放。
  2. parent_coroutine
    • 创建多个子协程任务并使用asyncio.gather运行。
    • try块:捕获子协程抛出的ValueError
    • except asyncio.CancelledError块:处理父协程被取消的情况,取消所有子协程任务并等待它们完成。
    • finally块:确保父协程取消时资源的释放。

通过上述方式,可以确保在父协程取消时,子协程的异常能够被正确捕获,并且资源能够得到合理释放。