asyncio aiohttp取消http请求轮询,返回结果 [英] asyncio aiohttp cancel an http request polling, return a result

查看:131
本文介绍了asyncio aiohttp取消http请求轮询,返回结果的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在使用此代码每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



  1. 将set_interval添加到事件循环后如何停止? (类似于javascript clearInterval()

  1. 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.



  1. set_interval 是否可能返回包装的函数的值?在此示例中,我需要响应文本。可以接受执行相同任务的其他模式。

  1. 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屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆