首次完成后取消两个异步协程

2022-01-15 00:00:00 python python-asyncio discord.py

问题描述

我正在用 Python 制作一个 Discord 机器人.我想做的是等待两个协程,一旦第一个完成运行其余代码但取消剩余的协程(不像 here 我只有一个协程并希望在特定时间后取消它.)p>

这是最小的可重现示例:

导入异步导入不和谐客户端 = 不和谐.客户端()异步定义测试函数():等待 asyncio.sleep(10)print("10 秒.")异步def testfunc2():等待 asyncio.sleep(15)打印(15 秒.")@client.eventasync def on_ready():打印(已连接!")等待 asyncio.wait([testfunc(),testfunc2()],return_when=asyncio.FIRST_COMPLETED)client.run('我的机器人令牌')

我打算在这个最小示例中发生的事情是打印10 seconds".10 秒后,然后不打印任何其他内容.然而,实际发生的是它也打印出15 seconds".15 秒后,我想取消那个协程,这样就不会发生.

在这种情况下,我似乎不能使用 asyncio.wait_for,因为有两个协程而不是一个,这两个协程必须同时运行,直到一个完成.由于 asyncio.wait 完成后不会取消协程,有什么办法可以取消它们?

解决方案

如果将协程包装在任务中,可以取消未完成的协程

@client.eventasync def on_ready():打印(已连接!")任务 = [asyncio.create_task(testfunc()), asyncio.create_task(testfunc2())]等待 asyncio.wait(任务,return_when=asyncio.FIRST_COMPLETED)对于任务中的任务:如果不是 task.done():任务.取消()

I am making a Discord bot in Python. What I want to do at one point is to await two coroutines, and once the first has completed run the rest of the code but cancel the remaining coroutine (unlike here where I just had one coroutine and wanted it to be cancelled after a specific time.)

Here is the minimum reproducible example:

import asyncio
import discord

client = discord.Client()

async def testfunc():
    await asyncio.sleep(10)
    print("10 seconds.")

async def testfunc2():
    await asyncio.sleep(15)
    print("15 seconds.")

@client.event
async def on_ready():
    print("Connected!")
    await asyncio.wait([testfunc(),testfunc2()],return_when=asyncio.FIRST_COMPLETED)

client.run('MY BOT TOKEN')

What I intend to happen in this minimal example is for it to print "10 seconds." after 10 seconds and then not print anything else. However, what actually happens is that it also prints "15 seconds." after 15 seconds, and I want to cancel that coroutine so that does not happen.

It appears that I cannot use asyncio.wait_for in this case as there are two coroutines instead of one, both of which must run simultaneously until one finishes. Since asyncio.wait does not cancel coroutines after one has finished, is there any way to cancel them?

解决方案

If you wrap the coroutines in tasks, you can cancel the ones that haven't finished

@client.event
async def on_ready():
    print("Connected!")
    tasks = [asyncio.create_task(testfunc()), asyncio.create_task(testfunc2())]
    await asyncio.wait(tasks,return_when=asyncio.FIRST_COMPLETED)
    for task in tasks:
        if not task.done():
            task.cancel()

相关文章