不看官方文档,这个问题你可能会束手无策

简介: 不看官方文档,这个问题你可能会束手无策

摄影:产品经理产品经理亲自下厨做的鸡 jio jio

在 Python 3.7版本开始,引入了新功能asyncio.run来快速运行一段异步代码。

例如对于一段使用 aiohttp 请求网址的代码,在 Python 3.6或者之前的版本,我们是这样写的:

import asyncio
import aiohttp
asyncdef main():
    asyncwith aiohttp.ClientSession() as client:
        resp = await client.get('http://httpbin.org/ip')
        ip = await resp.json()
        print(ip)
loop = asyncio.get_event_loop()
loop.run_until_complete(main())

运行效果如下图所示:


现在有了asyncio.run,我们可以少敲几次键盘:

import asyncio
import aiohttp
asyncdef main():
    asyncwith aiohttp.ClientSession() as client:
        resp = await client.get('http://httpbin.org/ip')
        ip = await resp.json()
        print(ip)
asyncio.run(main())

运行效果如下图所示:

这个功能真是太方便了!我准备完全不使用老式写法了。直到有一天,我使用 Motor 读取数据。

Motor 是用来异步读写 MongoDB 的库。我写代码一般会先写一段 Demo,确认没有问题了再把 Demo 改成正式代码。我们用 Motor写一段读取 MongoDB 的代码:

import asyncio
import motor.motor_asyncio
asyncdef main():
    client = motor.motor_asyncio.AsyncIOMotorClient()
    db = client.exercise
    collection = db.person_info
    asyncfor doc in collection.find({}, {'_id': 0}):
        print(doc)
asyncio.run(main())

运行效果符合预期,如下图所示:

既然 Demo 可以正常运行,那么我们把这段代码修改得稍微正式一些,使用类来包住正常的代码:

import asyncio
import motor.motor_asyncio
class MongoUtil:
    def __init__(self):
        conn = motor.motor_asyncio.AsyncIOMotorClient()
        db = conn.exercise
        self.collection = db.person_info
    async def read_people(self):
        async for doc in self.collection.find({}, {'_id': 0}):
            print(doc)
util = MongoUtil()
asyncio.run(util.read_people())

运行效果如下图所示,竟然报错了:

报错信息的最后一句,单独摘录出来::

RuntimeError: Task <Task pending coro=<MongoUtil.read_people() running at /Users/kingname/test_fastapi/test_motor.py:12> cb=[_run_until_complete_cb() at /Applications/Xcode.app/Contents/Developer/Library/Frameworks/Python3.framework/Versions/3.7/lib/python3.7/asyncio/base_events.py:158]> got Future <Future pending cb=[run_on_executor.._call_check_cancel() at /Users/kingname/.virtualenvs/test_fastapi-v5CW09hz/lib/python3.7/site-packages/motor/frameworks/asyncio/init.py:80]> attached to a different loop

其中最关键的一句话是:attached to a different loop

显然我们这个程序是单进程单线程的程序,这段报错说明当前的这个线程里面,在运行asyncio.run之前,就已经存在一个事件循环了。而根据 asyncio 的规定,一个线程里面只能有一个事件循环正在运行,所以就导致报错。

为了解释这个报错的原因,我们来看看 Python 的官方文档中,asyncio.run相关说明[1],如下图所示:

其中画红色方框的两个地方:

This function cannot be called when another asyncio event loop is running in the same thread. 当另一个 asyncio 事件循环正在当前线程运行的时候,不能调用这个函数。

This function always creates a new event loop and closes it at the end. 这个函数总是创建一个新的事件循环并在最后(运行完成)关闭它。

所以,当我们调用asyncio.run的时候,必须确保当前线程没有事件循环正在运行。

但是,当我们在运行上图第16行代码,初始化MongoUtil的时候,它的构造函数__init__会运行,于是第7行代码就会运行。

来看一下Motor 的官方文档中关于AsyncIOMotorClient描述[2]

AsyncIOMotorClient有一个参数叫做io_loop,如果不传入事件循环对象的话,就会使用默认的。但程序运行到这个位置的时候,还没有谁创建了事件循环,于是Motor就会自己创建一个事件循环。

关于这一点,大家可以阅读Motor 的源代码[3]第150-154行:

在不传入io_loop的时候,会调用self._framework.get_event_loop()。其中,self._framework可能是trio也可能是asyncio。因为 Motor 支持这两种异步框架。我们这里使用的是asyncio。由于当前没有正在运行的事件循环,所以asyncio.get_event_loop就会创建一个,并让它运行起来。

所以当我们使用 Motor 初始化 MongoDB 的连接时,就已经创建了一个事件循环了。但当代码运行到asyncio.run的时候,又准备创建一个新的事件循环,自然而然程序就运行错了。

所以,要让程序正常运行,我们在最后一行不能创建新的事件循环,而是需要获取由 Motor 已经创建好的事件循环。所以代码需要改成老式写法:

loop = asyncio.get_event_loop()
loop.run_until_complete(util.read_people())

这样一来,程序就能正常工作了:

这个问题通过官方文档就能找到原因并解决。但如果你不看官方文档,而是一味在网上乱搜索,恐怕很难找到解决办法。

目录
相关文章
|
Go
Go语言浮点数完全手册 float32和float64一文掌握!
Go语言浮点数完全手册 float32和float64一文掌握!
3158 0
|
Python
解决安装ConcurrentLogHandler报错error in ConcurrentLogHandler setup command: use_2to3 is invalid.
本文介绍了在Python环境下安装ConcurrentLogHandler时遇到的"use_2to3 is invalid"错误的解决方法,主要是通过降级setuptools到57.5.0版本来解决该问题。
544 2
|
7月前
|
存储 缓存 NoSQL
「缓存」会用很容易,用好才是技术活
本文对比了几种常用缓存的特点,主要介绍了基于Guava的本地缓存和基于Tair的分布式缓存,包含快速入门和深入原理两部分,并在最后提供了使用缓存时需要注意的事项。
|
Linux 开发工具 git
pip的常用命令和常见问题的解决
当使用pip命令安装Python包时,有时候可以通过使用镜像地址来加速下载速度或解决访问限制的问题。以下是一些常用的pip命令和常见的镜像地址:
1358 3
|
存储 缓存 安全
Android系统 应用存储路径与权限
Android系统 应用存储路径与权限
1686 0
Android系统 应用存储路径与权限
|
测试技术 虚拟化 iOS开发
iOS自动化测试方案(二):Xcode开发者工具构建WDA应用到iphone
这篇文章是iOS自动化测试方案的第二部分,详细介绍了在Xcode开发者工具中构建WebDriverAgent(WDA)应用到iPhone的全过程,包括环境准备、解决构建过程中可能遇到的错误,以及最终成功安装WDA到设备的方法。
892 0
iOS自动化测试方案(二):Xcode开发者工具构建WDA应用到iphone
|
机器学习/深度学习 自然语言处理 API
自然语言处理 Paddle NLP - 文本语义相似度计算(ERNIE-Gram)
自然语言处理 Paddle NLP - 文本语义相似度计算(ERNIE-Gram)
689 0
|
存储 数据可视化 数据处理
`geopandas`是一个开源项目,它为Python提供了地理空间数据处理的能力。它基于`pandas`库,并扩展了其对地理空间数据(如点、线、多边形等)的支持。`GeoDataFrame`是`geopandas`中的核心数据结构,它类似于`pandas`的`DataFrame`,但包含了一个额外的地理列(通常是`geometry`列),用于存储地理空间数据。
`geopandas`是一个开源项目,它为Python提供了地理空间数据处理的能力。它基于`pandas`库,并扩展了其对地理空间数据(如点、线、多边形等)的支持。`GeoDataFrame`是`geopandas`中的核心数据结构,它类似于`pandas`的`DataFrame`,但包含了一个额外的地理列(通常是`geometry`列),用于存储地理空间数据。