简体   繁体   English

Python asyncio 取消未等待的协程

[英]Python asyncio cancel unawaited coroutines

So given a bit of a complex setup, which is used to generate a list of queries to be run semi-parallel (using a semaphore to not run too many queries at the same time, to not DDoS the server).因此,给定一些复杂的设置,用于生成要半并行运行的查询列表(使用信号量来不同时运行太多查询,而不是对服务器进行 DDoS)。

i have an (in itself async) function that creates a number of queries:我有一个(本身是异步的)function,它创建了许多查询:

async def run_query(self, url):
    async with self.semaphore:
        return await some_http_lib(url)

async def create_queries(self, base_url):
   # ...gathering logic is ofc a bit more complex in the real setting
   urls = await some_http_lib(base_url).json()
 
   coros = [self.run_query(url) for url in urls]  # note: not executed just yet
   return coros

async def execute_queries(self):
   queries = await self.create_queries('/seom/url')
   _logger.info(f'prepared {len(queries)} queries')
   results = []
   done = 0

   # note: ofc, in this simple example call these would not actually be asynchronously executed.
   # in the real case i'm using asyncio.gather, this just makes for a slightly better
   # understandable example.
   for query in queries:
       # at this point, the request is actually triggered
       result = await query

       # ...some postprocessing

       if not result['success']:
           raise QueryException(result['message'])  # ...internal exception
       
       done  += 1
       _logger.info(f'{done} of {len(queries)} queries done')
       results.append(result)
    
   return results

Now this works very nicely, executing exactly as i planned and i can handle an exception in one of the queries by aborting the whole operation.现在这工作得很好,完全按照我的计划执行,我可以通过中止整个操作来处理其中一个查询中的异常。

async def run():
    try:
       return await QueryRunner.execute_queries()
    except QueryException:
       _logger.error('something went horribly wrong')
       return None

The only problem is that the program is terminated, but leaves me with the usual RuntimeWarning: coroutine QueryRunner.run_query was never awaited , because the queries later in the queue are (rightfully) not executed and as such not awaited.唯一的问题是程序被终止,但给我留下了通常的RuntimeWarning: coroutine QueryRunner.run_query was never awaited ,因为队列中稍后的查询(正确地)没有执行,因此没有等待。

Is there any way to cancel these unawaited coroutines?有没有办法取消这些未等待的协程? Would it be otherwise possible to supress this warning?否则是否有可能禁止此警告?

[Edit] a bit more context as of how the queries are executed outside this simple example: the queries are usually grouped together, so there is multiple calls to create_queries() with different parameters. [编辑] 关于如何在这个简单示例之外执行查询的更多上下文:查询通常分组在一起,因此有多个使用不同参数的 create_queries() 调用。 then all collected groups are looped and the queries are executed using asyncio.gather(group).然后循环所有收集的组并使用 asyncio.gather(group) 执行查询。 This awaits all the queries of one group, but if one fails, the other groups are canceled aswell, which results in the error being thrown.这会等待一个组的所有查询,但如果一个失败,其他组也会被取消,从而导致错误被抛出。

So you are asking how to cancel a coroutine that has not yet been either awaited or passed to gather .因此,您要问如何取消尚未等待或传递给gather的协程。 There are two options:有两种选择:

  • you can call asyncio.create_task(c).cancel()你可以调用asyncio.create_task(c).cancel()
  • you can directly call c.close() on the coroutine object你可以在协程 object上直接调用c.close()

The first option is a bit more heavyweight (it creates a task only to immediately cancel it), but it uses the documented asyncio functionality.第一个选项更重量级(它创建一个任务只是为了立即取消它),但它使用记录在案的 asyncio 功能。 The second option is more lightweight, but also more low-level.第二种选择更轻量级,但也更底层。

The above applies to coroutine objects that have never been converted to tasks (by passing them to gather or wait , for example).以上适用于从未转换为任务的协程对象(例如,通过将它们传递给gatherwait )。 If they have, for example if you called asyncio.gather(*coros) , one of them raised and you want to cancel the rest, you should change the code to first convert them to tasks using asyncio.create_task() , then call gather , and use finally to cancel the unfinished ones:如果他们有,例如,如果您调用asyncio.gather(*coros) ,其中一个提出并且您想取消 rest,您应该更改代码以首先使用asyncio.create_task()将它们转换为任务,然后调用gather ,并使用finally取消未完成的:

tasks = list(map(asyncio.create_task, coros))
try:
    results = await asyncio.gather(*tasks)
finally:
    # if there are unfinished tasks, that is because one of them
    # raised - cancel the rest
    for t in tasks:
        if not t.done():
            t.cancel()

Use利用

pending = asyncio.tasks.all_tasks()  # < 3.7 

or或者

pending = asyncio.all_tasks()  # >= 3.7 (not sure)

to get the list of pending tasks.获取待处理任务的列表。 You can wait for them with你可以等待他们

await asyncio.wait(pending, return_when=asyncio.ALL_COMPLETED)

or cancel them:或取消它们:

for task in pending:
    task.cancel()

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM