我有一个REST API包装器,应该在交互式Python会话中运行。 HTTP请求既可以通过自动后台线程(使用API包装器),也可以由最终用户通过交互式会话手动完成。我试图将所有HTTP请求管理从前一个新的每线程请求方法迁移到asyncio,但由于我无法在主线程中运行asyncio循环(它必须是免费的ad-hoc Python命令/请求),我写了以下内容在后台线程中运行它:
import aiohttp
import asyncio
from concurrent.futures import ThreadPoolExecutor
def start_thread_loop(pool=None):
"""Starts thread with running loop, bounding the loop to the thread"""
def init_loop(loop):
asyncio.set_event_loop(loop) # bound loop to thread
loop.run_forever()
_pool = ThreadPoolExecutor() if pool is None else pool
loop = asyncio.new_event_loop()
future = _pool.submit(init_loop, loop)
return future, loop
def send_to_loop(coro, loop):
"""Wraps couroutine in Task object and sends it to given loop"""
return asyncio.run_coroutine_threadsafe(coro, loop=loop)
实际的API包装器类似于以下内容:
class Foo:
def __init__(self):
_, self.loop = start_thread_loop()
self.session = aiohttp.ClientSession(loop=self.loop)
self.loop.set_debug(True)
def send_url(self, url):
async def _request(url):
print('sending request')
async with self.session.get(url) as resp:
print(resp.status)
return send_to_loop(_request(url), self.loop)
但是,aiohttp
强烈建议不要制作ClientSession
在初始化asyncio
之前,在协程之外并启用ClientSession
调试模式会引发RuntimeError
。因此,我尝试使用asycio.Queue
制作略有不同的版本,以避免在协程中生成ClientSession
:
class Bar:
def __init__(self):
_, self.loop = start_thread_loop()
self.q = asyncio.Queue(loop=self.loop)
self.status = send_to_loop(self.main(), loop=self.loop)
async def main(self):
async with aiohttp.ClientSession(loop=self.loop) as session:
while True:
url = await self.q.get()
print('sending request')
asyncio.ensure_future(self._process_url(url, session), loop=self.loop)
def send_url(self, url):
send_to_loop(self.q.put(url), loop=self.loop)
@staticmethod
async def _process_url(url, session):
async with session.get(url) as resp:
print(resp.status)
然而,这种方法更加复杂/冗长,我真的不明白它是否真的有必要。
问题:
ClientSession
会出现问题?答案 0 :(得分:3)
为什么在协程之外启动ClientSession会出现问题?
如何构建aiohttp,理论上应该可以在循环之外初始化某种客户端会话,即。在协程之外,但不是aiohttp的构建方式。 AFAIU位于the issue that introduced this warning,因为a)难以测试b)它容易出错
队列是否更好/更安全?如果是这样,为什么?
我不明白你想要达到的目标,所以我不知道如何回答。也许您遇到的问题是您尝试在构造函数内初始化ClientSession
。另一个班级__init__
。在这种情况下,您应该通过创建一个辅助方法来解决该问题,该方法是一个完成类初始化的协程。使用异步代码时,这是已知的模式。
我在后台线程中启动循环的方法有问题吗?
完全可以。