如何在不阻塞的情况下发出请求(使用 asyncio)?

Posted

技术标签:

【中文标题】如何在不阻塞的情况下发出请求(使用 asyncio)?【英文标题】:How to make request without blocking (using asyncio)? 【发布时间】:2017-10-11 16:50:38 【问题描述】:

我想使用asyncio实现以下目标:

# Each iteration of this loop MUST last only 1 second
while True:
    # Make an async request

    sleep(1)

但是,我看到的唯一示例使用了一些变体

async def my_func():
    loop = asyncio.get_event_loop()
    await loop.run_in_executor(None, requests.get, 'http://www.google.com')

loop = asyncio.get_event_loop()
loop.run_until_complete(my_func())

但是run_until_complete 正在阻止!在我的while 循环的每次迭代中使用run_until_complete 会导致循环阻塞。

过去几个小时我一直在试图弄清楚如何正确运行非阻塞任务(使用async def 定义)但没有成功。我一定遗漏了一些明显的东西,因为像这样简单的东西肯定应该很简单。我怎样才能实现我所描述的?

【问题讨论】:

循环本身总是阻塞的。理想情况下,您希望将您的 while 放在循环运行的协程中,但如果没有更好地了解您的代码应该如何工作,很难说更多。 你好,我发现自己正处于这种情况,我想要的只是正常的同步执行...(在一个while循环内)...with在需要时调用异步方法的能力。我不在乎结果。你能做到吗???请,谢谢 javascript 中这很容易实现,可惜 Python 让它变得如此困难。 【参考方案1】:

run_until_complete 运行主事件循环。可以说它不是“阻塞”,它只是运行事件循环,直到您作为参数传递的协程返回。它必须挂起,否则程序要么停止,要么被下一条指令阻塞。

很难说你愿意实现什么,但这段代码确实做了一些事情:

async def my_func():
    loop = asyncio.get_event_loop()
    while True:
        res = await loop.run_in_executor(None, requests.get, 'http://www.google.com')
        print(res)
        await asyncio.sleep(1)
loop = asyncio.get_event_loop()
loop.run_until_complete(my_func())

它将每秒钟在 Google 主页上执行一次 GET 请求,弹出一个新线程来执行每个请求。您可以通过虚拟并行运行多个请求来说服自己它实际上是非阻塞的:

async def entrypoint():
    await asyncio.wait([
        get('https://www.google.com'),
        get('https://www.***.com'),
    ])

async def get(url):
    loop = asyncio.get_event_loop()
    while True:
        res = await loop.run_in_executor(None, requests.get, url)
        print(url, res)
        await asyncio.sleep(1)

loop = asyncio.get_event_loop()
loop.run_until_complete(entrypoint())

要注意的另一件事是,您每次都在单独的线程中运行请求。它有效,但它有点像黑客。您应该使用真正的异步 HTTP 客户端,例如 aiohttp。

【讨论】:

谢谢,这让事情变得更清楚了,但这难道不是我的while 循环是异步的(当我不希望它是异步的时,如我的示例所示)?我想要的只是正常的同步执行,并且能够在需要时调用异步方法。我不在乎结果。使用线程这很简单 - Thread().start()。也许使用asyncio 只需要我的心态发生重大变化。 asyncio 确实需要您更改应用程序的整个范例。想象一下你打电话给run_until_complete,它没有挂起。在它之后,您可以调用time.sleep(5),它是一个阻塞函数。事实上,你之后调用的任何函数都不会给事件循环轮询的机会。协程永远不会执行,因为事件循环没有运行/轮询(记住:所有这一切都发生在一个线程上)。如果您稍后需要调用阻塞函数,请在另一个线程中执行(使用run_in_executor)或在另一个线程中运行整个事件循环。

以上是关于如何在不阻塞的情况下发出请求(使用 asyncio)?的主要内容,如果未能解决你的问题,请参考以下文章

如何在不公开 API 密钥的情况下发出 POST 请求?

如何让 Playframework 在不发出请求的情况下开始在开发模式下运行应用程序?

CORS 谷歌浏览器扩展有啥替代品吗?如何在不使用 CORS 的情况下成功发出 ajax 请求?

如何在不阻塞的情况下从 Spring Webflux 中的 Mono 对象中提取数据?

如何在不冻结 GUI 的情况下在单个插槽中实现阻塞进程?

如何在不发出单独请求的情况下将元数据和音轨从广播流中分离出来