简体   繁体   English

协同之外的Aiohttp ClientSession

[英]Aiohttp ClientSession outside coroutine

I have a REST API wrapper which is supposed to run in an interactive Python session. 我有一个REST API包装器,应该在交互式Python会话中运行。 HTTP requests are made both through an automated background thread (which uses the API wrapper) and manually by the end user through the interactive session. HTTP请求既可以通过自动后台线程(使用API​​包装器),也可以由最终用户通过交互式会话手动完成。 I am trying to migrate all HTTP request management to asyncio from the former new-thread-per-request approach, but since I can't run the asyncio loop in the main thread (it has to be free for ad-hoc Python commands/requests), I wrote the following to run it in a background thread: 我试图将所有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)

The actual API wrapper is something like the following: 实际的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)

However, aiohttp highly recommends against making a ClientSession outside of coroutine and turning on asyncio debug mode before initializing ClientSession raises a RuntimeError . 但是, aiohttp强烈建议不要在协程之外创建ClientSession并在初始化ClientSession之前启用asyncio调试模式会引发RuntimeError Therefore, I tried making a slightly different version using asycio.Queue in order to avoid making a ClientSession inside a coroutine: 因此,我尝试使用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)

However, this approach is more complex/verbose and I don't really understand if it is actually necessary. 但是,这种方法更加复杂/冗长,我真的不明白它是否真的有必要。

Questions: 问题:

  1. Why is it a problem starting a ClientSession outside of a coroutine? 为什么在协程之外启动ClientSession是一个问题?
  2. Is the queue approach better/safer? 队列方法更好/更安全吗? If so, why? 如果是这样,为什么?
  3. Is there any problem in my approach for starting a loop inside a background thread? 我在后台线程中启动循环的方法有什么问题吗?

Why is it a problem starting a ClientSession outside of a coroutine? 为什么在协程之外启动ClientSession是一个问题?

That's how aiohttp is built, in theory it should be possible to initialize some kind of client session outside a loop ie. 这就是aiohttp的构建方式,理论上应该可以在循环之外初始化某种客户端会话,即。 outside a coroutine but it's not how aiohttp is built. 在协程之外,但不是aiohttp的构建方式。 AFAIU in the issue that introduced this warning , it's because a) it's difficult to test b) it's prone to error AFAIU在引入此警告的问题中 ,这是因为a)难以测试b)它容易出错

Is the queue approach better/safer? 队列方法更好/更安全吗? If so, why? 如果是这样,为什么?

I don't understand what you try to achieve so I am not sure how to answer. 我不明白你想要达到什么目标,所以我不知道如何回答。 Maybe the problem you have is that you try to initialize a ClientSession inside the constructor aka. 也许您遇到的问题是您尝试在构造函数内初始化ClientSession __init__ of another class. __init__另一个班级。 In this case, you should work around that issue by creating a helper method that is a coroutine that will finish the initialization of the class. 在这种情况下,您应该通过创建一个辅助方法来解决该问题,该方法是一个完成类初始化的协程。 This is known pattern when working with async code. 使用异步代码时,这是已知的模式。

Is there any problem in my approach for starting a loop inside a background thread? 我在后台线程中启动循环的方法有什么问题吗?

It's perfectly ok. 这完全没问题。

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

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