asyncio aiohttp取消http请求轮询,返回结果 [英] asyncio aiohttp cancel an http request polling, return a result
问题描述
我正在使用此代码每5秒创建一个http请求。
I'm using this code to create an http request every 5 seconds.
async def do_request():
async with aiohttp.ClientSession() as session:
async with session.get('http://localhost:8000/') as resp:
print(resp.status)
return await resp.text()
没有找到内置的调度程序,所以我写了这个函数(类似于javascript):
Didn't find a bulit-in scheduler, so I wrote this function (similar to javascript):
async def set_interval(fn, seconds):
while True:
await fn()
await asyncio.sleep(seconds)
这就是我的方法使用它:
And this is how I use it:
asyncio.ensure_future(set_interval(do_request, 5))
代码可以正常工作,但我有两个要求:
1.如何将set_interval添加到事件中后停止环? (类似于javascript clearInterval()
)
2. set_interval是否有可能返回包装的函数的值?在此示例中,我需要响应文本。可以接受执行相同任务的其他模式。
The code works fine, but I have two requirements:
1. How can I stop the set_interval after it is added to the event loop? (similar to javascript clearInterval()
)
2. Is it possible that set_interval will return the value of the function that it is wrapping? In this example I will want the response text. Other pattern that will do the same task will be accepted.
推荐答案
取消工作
Canceling the job
- 将set_interval添加到事件循环后如何停止? (类似于javascript
clearInterval()
)
- How can I stop the set_interval after it is added to the event loop? (similar to javascript
clearInterval()
)
一种选择是取消返回的任务:
One option is to cancel the returned task:
# create_task is like ensure_future, but guarantees to return a task
task = loop.create_task(set_interval(do_request, 5))
...
task.cancel()
这还将取消将来任何等待的 set_interval
。如果您不希望这样做,并且希望 fn()
继续在后台运行,请使用 await asyncio.shield(fn())<
This will also cancel whatever future set_interval
was awaiting. If you don't want that, and want fn()
to continue in the background instead, use await asyncio.shield(fn())
instead.
-
set_interval
是否可能返回包装的函数的值?在此示例中,我需要响应文本。可以接受执行相同任务的其他模式。
- Is it possible that
set_interval
will return the value of the function that it is wrapping? In this example I will want the response text. Other pattern that will do the same task will be accepted.
由于 set_interval
在无限循环中运行,它无法返回任何内容-返回将终止循环。
Since set_interval
is running in an infinite loop, it cannot return anything - a return would terminate the loop.
如果需要函数中的值,一种选择是将 set_interval
重新设计为生成器。调用者将使用 async for
获取值,这很容易理解,但与JavaScript的 setInterval
却大不相同:
If you need the values from the function, one option is to redesign set_interval
as a generator. The caller would obtain the values using async for
, which is quite readable, but also very different from JavaScript's setInterval
:
async def iter_interval(fn, seconds):
while True:
yield await fn()
await asyncio.sleep(seconds)
用法示例:
async def x():
print('x')
return time.time()
async def main():
async for obj in iter_interval(x, 1):
print('got', obj)
# or asyncio.get_event_loop().run_until_complete(main())
asyncio.run(main())
通过Future广播值
另一种方法是在循环的每个遍历中将生成的值广播到其他协程可以等待的全球未来。
Broadcasting values via a future
Another approach is for each pass of the loop to broadcast the generated value to a global future which other coroutines can await; something along the lines of:
next_value = None
async def set_interval(fn, seconds):
global next_value
loop = asyncio.get_event_loop()
while True:
next_value = loop.create_task(fn())
await next_value
await asyncio.sleep(seconds)
用法如下:
# def x() as above
async def main():
asyncio.create_task(set_interval(x, 1))
while True:
await asyncio.sleep(0)
obj = await next_value
print('got', obj)
上面的简单实现有一个主要问题:提供下一个值后, next_value
就不存在了。立即被新的 Future
所取代,但要在睡觉后才能进行。这意味着 main()
会在紧密循环中打印 got,直到出现新的时间戳。这也意味着删除 await asyncio.sleep(0)
实际上会破坏它,因为循环中唯一的 await
会永远不会暂停,并且 set_interval
将不再有运行的机会。
The above simple implementation has a major issue, though: Once the next value is provided, next_value
is not immediately replaced by a new Future
, but only after the sleep. This means that main()
prints "got " in a tight loop, until a new timestamp arrives. It also means removing await asyncio.sleep(0)
would actually break it, because the only await
in the loop would never suspend and set_interval
would no longer get a chance to run.
这显然不是故意的。我们希望即使在获得初始值之后,在 main()
中的循环也要 wait 。为此, set_interval
必须更聪明:
This is clearly not intended. We would like the loop in main()
to wait for the next value, even after an initial value is obtained. To do so, set_interval
must be a bit smarter:
next_value = None
async def set_interval(fn, seconds):
global next_value
loop = asyncio.get_event_loop()
next_value = loop.create_task(fn())
await next_value
async def iteration_pass():
await asyncio.sleep(seconds)
return await fn()
while True:
next_value = loop.create_task(iteration_pass())
await next_value
此版本可确保在等待前一个值后立即将 next_value
分配给该值。为此,它使用了辅助程序 iteration_pass
协程,该协程很方便,可以在<$ c之前放入 next_value
$ c> fn()实际上已经可以运行了。将其放置在适当位置后, main()
可能看起来像这样:
This version ensures that the next_value
is assigned to as soon as the previous one has been awaited. To do so, it uses a helper iteration_pass
coroutine which serves as a convenient task to put into next_value
before fn()
is actually ready to run. With that in place, main()
can look like this:
async def main():
asyncio.create_task(set_interval(x, 1))
await asyncio.sleep(0)
while True:
obj = await next_value
print('got', obj)
main()
不再忙循环,其预期输出恰好是每秒一个时间戳。但是,我们仍然需要 initial asyncio.sleep(0)
,因为 next_value
很简单当我们仅调用 create_task(set_interval(...))
时不可用。这是因为使用 create_task
安排的任务仅在返回事件循环后才运行。忽略 sleep(0)
会导致错误,例如无法等待 NoneType
类型的对象 。
main()
is no longer busy-looping and has the expected output of exactly one timestamp per second. However, we still need the initial asyncio.sleep(0)
because next_value
is simply not available when we just call create_task(set_interval(...))
. This is because a task scheduled with create_task
is only run after returning to the event loop. Omitting the sleep(0)
would result in an error along the lines of "objects of type NoneType
cannot be awaited".
要解决此问题,可以将 set_interval
拆分为常规的 def
计划初始循环迭代并立即初始化 next_value
。函数实例化并立即返回完成其余工作的协程对象。
To resolve this, set_interval
can be split into a regular def
which schedules the initial loop iteration and immediately initializes next_value
. The function instantiates and immediately returns a coroutine object that does the rest of the work.
next_value = None
def set_interval(fn, seconds):
global next_value
loop = asyncio.get_event_loop()
next_value = loop.create_task(fn())
async def iteration_pass():
await asyncio.sleep(seconds)
return await fn()
async def interval_loop():
global next_value
while True:
next_value = loop.create_task(iteration_pass())
await next_value
return interval_loop()
现在 main()
可以用显而易见的方式编写:
Now main()
can be written in the obvious way:
async def main():
asyncio.create_task(set_interval(x, 1))
while True:
obj = await next_value
print('got', obj)
与异步迭代相比,这种方法的优点是,多个侦听器可以观察这些值。
Compared to async iteration, the advantage of this approach is that multiple listeners can observe the values.
这篇关于asyncio aiohttp取消http请求轮询,返回结果的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!