การยกเลิกเฉพาะงานหลักในโปรแกรมอะซินซิโอ

โดยปกติ หาก Coroutine เริ่มทำงานโดยใช้ฟังก์ชัน asyncio.run(coroutine) การขัดจังหวะด้วยแป้นพิมพ์ (CTRL + C) หรือ SIGINT จะยกเลิกงานที่ค้างอยู่ทั้งหมดในลูปเหตุการณ์ ฉันกำลังมองหาวิธีที่เฉพาะงานหลัก (งานที่ส่งผ่านไปยัง asyncio.run(coroutine)) เท่านั้นที่จะถูกยกเลิก แนวคิดก็คืองานหลักจะจัดการยกเลิกงานย่อยทั้งหมดตามลำดับที่เห็นสมควร

ลองพิจารณาตัวอย่าง:

import asyncio


async def main():
    foo_task = asyncio.create_task(foo())
    try:
        await asyncio.sleep(10)
        print('main finished')
    finally:
        print('ensuring foo task is finished')
        await foo_task


async def foo():
    await asyncio.sleep(10)
    print('foo finished')


try:
    asyncio.run(main())
except KeyboardInterrupt:
    pass

ฉันต้องการเปลี่ยนโค้ดด้านบนเพื่อที่ว่าหากมีการขัดจังหวะแป้นพิมพ์หรือ SIGINT ถูกส่งไประหว่างการดำเนินการ foo_task จะยังคงเสร็จสิ้น ควรพิมพ์สิ่งต่อไปนี้:

ensuring foo task is finished
foo finished

ฉันไม่ต้องการใช้การป้องกัน (asyncio.shield(coroutine)) เพราะฉันต้องการให้งานหลักควบคุมลำดับการยกเลิก/ดำเนินการงานย่อยได้อย่างสมบูรณ์


person Jaanus Varus    schedule 15.03.2021    source แหล่งที่มา


คำตอบ (1)


ฉันไม่รู้ว่าเป็นความคิดที่ดีหรือไม่ แต่สำหรับระบบปฏิบัติการที่คล้าย Unix คุณสามารถบรรลุพฤติกรรมที่ต้องการได้ด้วยตัวจัดการสัญญาณ

import asyncio
from asyncio import tasks
import signal
from typing import Coroutine, Set

to_cancel: Set[Coroutine] = set()  # little workaround to detect the main task

async def main():
    loop = asyncio.get_event_loop()
    loop.add_signal_handler(signal.SIGINT, cancel_main)
    loop.add_signal_handler(signal.SIGTERM, cancel_main)

    foo_task = asyncio.create_task(foo())

    try:
        print("main sleeping")
        await asyncio.sleep(10)
    except asyncio.CancelledError:
        print("main cancelled")
    finally:
        print('ensuring foo task is finished')
        await foo_task
        print('main finished')


async def foo():
    print("foo sleeping")
    await asyncio.sleep(10)
    print("foo finished")


def cancel_main():
    for task in tasks.all_tasks():
        # task.get_coro() for python >= 3.8 else task._coro
        if task.get_coro() in to_cancel and not task.cancelled():
            task.cancel()

if __name__ == "__main__":
    coro = main()
    to_cancel.add(coro)
    asyncio.run(coro)
    

ผลลัพธ์

main sleeping
foo sleeping
^C
main cancelled
ensuring foo task is finished
foo finished
main finished
person NobbyNobbs    schedule 15.03.2021