Python 异步IO任务已销毁,但它处于挂起状态

Python 异步IO任务已销毁,但它处于挂起状态,python,asynchronous,python-asyncio,coroutine,event-loop,Python,Asynchronous,Python Asyncio,Coroutine,Event Loop,我正在使用一个示例程序,它从数据源(csv或rdbms)中读取数据,进行一些转换,并通过套接字将其发送到服务器 但是因为csv非常大,出于测试的目的,我想在几块之后中断读取。 不幸的是,有些地方出了问题,我不知道该怎么解决。也许我必须取消一些,但现在确定在哪里以及如何取消。我得到以下错误: Task was destroyed but it is pending! task: <Task pending coro=<<async_generator_athrow without

我正在使用一个示例程序,它从数据源(csv或rdbms)中读取数据,进行一些转换,并通过套接字将其发送到服务器

但是因为csv非常大,出于测试的目的,我想在几块之后中断读取。 不幸的是,有些地方出了问题,我不知道该怎么解决。也许我必须取消一些,但现在确定在哪里以及如何取消。我得到以下错误:

Task was destroyed but it is pending!
task: <Task pending coro=<<async_generator_athrow without __name__>()>>

问题很简单。您确实提前退出了循环,但异步生成器尚未耗尽(其挂起):


许多
async
资源(如生成器)需要借助事件循环进行清理。当
async for
循环停止通过
break
迭代异步生成器时,该生成器仅由垃圾收集器清理。这意味着任务处于挂起状态(等待事件循环),但被(垃圾收集器)销毁

最直接的修复方法是显式关闭生成器:

async def main():
    i = 0
    aiter = readChunks()      # name iterator in order to ...
    try:
        async for chunk in aiter:
            ...
            i += 1
            if i > 5:
                break
    finally:
        await aiter.aclose()  # ... clean it up when done

可以使用(免责声明:我维护这个库)简化这些模式。允许在完全关闭发电机之前获取固定数量的项目:

import asyncstdlib as a

async def main():
    async for chunk in a.islice(readChunks(), 5):
        ...
如果
中断
条件是动态的,则保证在任何情况下进行清理:

import asyncstdlib as a

async def main():
    async with a.scoped_iter(readChunks()) as aiter:
        async for idx, chunk in a.enumerate(aiter):
            ...
            if idx >= 5:
                break

您的
readChunks
正在异步和循环中运行。没有完成程序,你就是在破坏它

这就是为什么异步IO任务已被销毁,但仍处于挂起状态

简言之,异步任务在后台工作,但您通过中断循环(停止程序)终止了它。

这是可行的

import asyncio
import json
import logging

logging.basicConfig(format='%(asctime)s.%(msecs)03d %(message)s',
                    datefmt='%S')
root = logging.getLogger()
root.setLevel(logging.INFO)

async def readChunks():
  # this is basically a dummy alternative for reading csv in chunks
  df = [{"chunk_" + str(x) : [r for r in range(10)]} for x in range(10)]
  for chunk in df:
    await asyncio.sleep(0.002)
    root.info('readChunks: next chunk coming')
    yield chunk

async def send(row):
    j = json.dumps(row)
    root.info(f"to be sent: {j}")
    await asyncio.sleep(0.002)


async def main():
    i = 0
    root.info('main: starting to read chunks')
    async for chunk in readChunks():
        for k, v in chunk.items():
            root.info(f'main: sending an item')
            #await asyncio.gather(send({k:v}))
            stuff = await send({k:v})
        i += 1
        if i > 5:
            break
        #print(f"item in main via async generator is {chunk}")

##loop = asyncio.get_event_loop()
##loop.run_until_complete(main())
##loop.close()

if __name__ == '__main__':

    asyncio.run(main())
。。。至少它运行并完成了


在bugs.python.org/issue38013中描述了通过解除
async for
循环来停止异步生成器的问题,看起来它在3.7.5中已经修复

然而,使用

loop = asyncio.get_event_loop()
loop.set_debug(True)
loop.run_until_complete(main())
loop.close()
我在Python3.8中得到一个调试错误,但没有异常

Task was destroyed but it is pending!
task: <Task pending name='Task-8' coro=<<async_generator_athrow without __name__>()>>
任务已销毁,但它处于挂起状态!

任务:
不幸的是出了问题…
-出了什么问题?这是怎么一回事?你知道你的合作项目是否按照你想要的方式工作吗?你是否试图通过检查中间结果来追踪错误?为什么不使用高级api-
asyncio.run()
?@7u5h4r,亚历克斯:是的,我的目的是破坏迭代。但我现在的做法是。。。至少很糟糕。我在寻找合适的方法。所以我想找到取消的方法,在哪里,如何取消,以及取消的部分。我不知道
asyncstdlib
,干得好,Mistermiagi!非常感谢。想做一些取消的事情,并将其转换为任务。还不知道是什么和怎么做的。你知道任何详细的,但一步一步的指南,它越来越深入,并引导我通过异步世界?还发现这似乎是工作的w/o“最终”:等待asyncio.gather(发送({k:v}),返回_异常=真)你有调试吗让我试一试-我使用的是3,8.Nothing报告,调试模式打开-我不确定在我的计算机上没有3.7的情况下我是否可以跟踪该问题,我会查看文档。@Mistermiagi-看起来像是-它出现在3.7.1中,然后在3.7.5中修复。忘记提到我使用的是3.7,因为通过使用3.8,我无法安装一些可用C代码的LIB。@spyder-看起来像是在3.7.5中修复的-如果它不会破坏您的库,请升级到3.7.5-3.7.9。
loop = asyncio.get_event_loop()
loop.set_debug(True)
loop.run_until_complete(main())
loop.close()
Task was destroyed but it is pending!
task: <Task pending name='Task-8' coro=<<async_generator_athrow without __name__>()>>