有这样一个协程
async def function(**kwargs):
session = aiohttp.ClientSession()
response = await session.request(**kwargs)
pass
如果使用 asyncio.run(function(arg=bala))和 new_event_loop 会出现
RuntimeError: Timeout context manager should be used inside a task
使用
loop = asyncio.get_event_loop()
loop.run_until_complete(function(arg=bala))
loop.close()
不会出现上下文管理的问题 这两者的区别在哪里,翻了半天官方文档和 csdn 的转载文实在无法理解 TAT
1
Lycnir 2021-02-03 16:59:20 +08:00
python3.7 正常
|
2
cissoid 2021-02-03 17:00:23 +08:00 1
翻翻官方文档,
asyncio.run: This function cannot be called when another asyncio event loop is running in the same thread. |
3
linw1995 2021-02-03 17:23:30 +08:00 1
应该是因为你把同个 ClientSession 用在两个不同的 eventloop 上。asyncio.run 跑完后,event_loop 会关闭掉。在新的 event_loop 跑的时候,因为用的同一个 ClientSession,它用了上一个关闭了的 event_loop,导致这个异常抛出。
你这个是旧版的 aiohttp 吧,新版的 async_timeout 不会出现这个问题。 |
4
RandomAccess OP DeprecationWarning: The object should be created within an async function
看样子应该是 session 在类实例化时在普通函数中创建的产生的问题,一个包含异步函数的对象在只能在 async 函数中创建,包括自己封装的类 |
5
ClericPy 2021-02-03 21:06:15 +08:00 1
class TimerContext(BaseTimerContext):
""" Low resolution timeout context manager """ def __init__(self, loop: asyncio.AbstractEventLoop) -> None: raise RuntimeError( "Timeout context manager should be used " "inside a task" ) 老问题了, 所以我现在用到 aiohttp 尽量不用 asyncio.run, 因为它一定新建一个 loop, 然而 Session 的默认 Timeout 对象却是在协程外面初始化的, 导致两个循环不一致, 见下面源码 https://github.com/aio-libs/aiohttp/blob/742a8b6d09b2623670ddede838c913d2a8a4d89e/aiohttp/client.py#L161 据说 aiohttp 4.0 以后会好, 但是 4.0 一直发不出来 想解决的话, 简单的就是暂时别用 asyncio.run. 或者在协程函数里 import 它, 或者手动指定 Timeout 试试 |
6
RandomAccess OP @ClericPy 谢谢大佬
如果业务场景需要 session 在两个阻塞的事件循环中传递是不是不可行的 |
7
guochao 2021-02-04 10:48:44 +08:00 1
简单说 python 的 event loop 是一个单进程单线程单个 loop 执行 coroutine 的过程。asyncio.run 就是一个创建 loop 并且 run until complete 的过程,new event loop 又建了一个,没有必要。
解决办法就是让整个程序的入口是一个 async 函数,在 async 函数中配置程序,新建各种实例,启动应用。在__main__里面 asyncio.run(entrypoint())就行。然后在任何其他地方都不要 new_event_loop 或者 asyncio.run 如果要执行一个 coroutine 但是不等待结束,可以 asyncio.ensure_future 或者 3.7 以上的新 API asyncio.create_task,这两个函数都是在 get_event_loop()返回的 loop 上执行对应的函数,会返回一个 Task 如果要在一个 coroutine 中执行一个 coroutine 并且等待结束,那就直接 await 。 如果要执行一个同步过程,可以用 run_in_executor,返回一个 Future |
8
RandomAccess OP @guochao thanks
|
9
ClericPy 2021-02-04 20:30:21 +08:00 1
@RandomAccess
时间循环尽可能只用一个, 就算协程用的很熟悉的人, 也很少去多个线程上跑多个 loop 所以 Python 3.9 还是 3.10 之后, 很多内置的协程方法都去掉了 loop 参数, 默认都从 running loop 里面获取 你要做的是, 首先保证整个程序只留有一个事件循环(因为多个也没有意义, await 不是阻塞, 是等待), 然后在里面传递 session 对象就是合法的了 然后你说的阻塞, 协程实际上是非阻塞的设计, 你的阻塞可能是 await 关键词, 那个是等待不算阻塞. 所以你如果想并发开始多个任务, 可以把 "协程函数" (async def 声明的)执行获得的 "协程对象" 创建为 Task, 它就会开始执行但是又不会阻塞, 创建 Task 的方式有 asyncio.ensure_future(some_coro) 或者 asyncio.create_task(coro), 后者是 3.7 以后新增的 |
10
RandomAccess OP @ClericPy 确实是应该一个进程或线程只使用一个事件循环,只是业务场景需要进行一次长 io 且逻辑复杂的操作 A 获取操作 B 所需要的必要参数,然后任务 B 去执行多并发的也是长 io 的操作,AB 有着前后顺序,逻辑上应当是阻塞的,但是各自内部操作是无序的长 io,想尝试多个事件循环是否适应这个场景或多个不同并发的协程有没有好的顺序执行方案
|
11
RandomAccess OP @RandomAccess 操作 A 和 B 各自都是嵌套的协程
|
12
ClericPy 2021-02-04 21:30:20 +08:00 1
|
13
RandomAccess OP @ClericPy thanks
|
14
RandomAccess OP @ClericPy 对协程的理解不是很深刻,刚才想通 await 的等待交出执行权其实也可以在代码逻辑实现前后顺序,没必要复杂化多个事件循环
|