【问题标题】:asyncio create_task to run foreverasyncio create_task 永远运行
【发布时间】:2018-05-29 20:35:17
【问题描述】:

我有以下代码

import asyncio

loop = asyncio.get_event_loop()

async def while_loop():
    n = 0
    while True:
        print(f"{n}")
        await asyncio.sleep(2)
        n = n+1

async def some_func():
    await asyncio.sleep(5)
    print("Some Func")

future = loop.create_task(while_loop())
loop.run_until_complete(some_func())

我预计while_loop 函数会永远运行,但它似乎只是由于调用run_until_complete 而执行,并且一旦some_func 完成执行,它就会停止打印while 循环。我看到的输出是:

0
1
2
Some Func

我预计即使在 some_func 完成后,这些数字也会继续打印。

0
1
2
Some Func
3
4
5
6
.
.
.

打印更多号码的唯一方法是再次致电some_func

【问题讨论】:

    标签: python python-3.x python-asyncio


    【解决方案1】:

    我预计即使在 some_func 完成后,这些数字也会继续打印。

    run_until_complete 的参数控制事件循环的运行时间。一旦事件循环停止运行,所有协程都会被有效地挂起,而不仅仅是你一直在等待的那个。但你确实有不同的选择:

    • loop.run_until_complete(some_func()) - 你已经用过的;运行事件循环,直到 some_func 协程完成。在此期间也会并行执行其他协程,但也会在事件循环结束后立即停止执行。

    • loop.run_forever() - 运行事件循环,直到某个协程或回调调用 loop.stop()。如果他们都没有这样做,那么即使所有协程都结束了,事件循环也不会停止。在你的情况下,你会打电话给loop.create_task(while_loop()),然后是loop.create_task(some_func()),然后是loop.run_forever()

    • loop.run_until_complete(asyncio.gather(while_loop(), some_func())) 运行事件循环,直到 both 指定的协程完成。这(等待 所有 任务)显然是您期望 loop.run_until_complete() 自动执行的操作,即使您只命名一个,但它不是那样工作的,它会在指定的协程后立即停止完成。 asyncio.gather 可用于一次等待多个协程。如需更精细的等待控制,另请参阅asyncio.wait

    由于您的协程之一永远运行,最后两个选项将是等效的,并将产生预期的输出。

    【讨论】:

    • 现在,如果我想稍后在某个随机时间运行some_func(和其他函数),那么最好在单独的线程中运行一个新循环吗?否则,run_forever 会阻塞主线程循环。
    • @slaw 考虑协程可以启动其他协程。因此,您调用run_forever 的事实并不意味着整个程序被卡住或者您不能为事件循环添加新的东西。典型的 asyncio 程序是围绕为整个应用程序服务的事件循环构建的。仅当您需要将 asyncio 引入遗留代码库(问题未指出)时,才需要在后台线程中使用事件循环的整个过程。
    • 嗯,我想知道当我在 Jupyter 笔记本中测试我的代码时是否看到了奇怪的行为。当我在 loop.get_event_loop() 之后执行它时,loop.run_forever() 会无限期地阻塞。我希望执行 loop.run_forever() 然后能够创建添加到循环中的任务。这是错误的思考方式吗?本质上,我正在探索从 RESTful API 检索到的数据,但我不知道这些数据是如何布局的。所以我点击了 1000 个 url 来收集数据,然后分析它以了解我接下来可能想要抓取的其他端点。
    • @slaw 请记住,asyncio 不是线程化的 - 您不能同时完成 run_until_complete() 并让数字继续打印。 asyncio 旨在让整个程序在事件循环中运行。因此,如果您正在探索使用 REST 检索的数据,请编写一个检索数据并分析数据的协程(如果需要,通过生成其他协程)。这个协程可以包含一个简单的while True 循环,也可以包含任意复杂的逻辑。使用loop.create_task() 启动协程,运行loop.run_forever(),然后让它完成剩下的工作。
    • run_until_complete()阻塞了主线程之后,终于清楚地解释了如何将任务添加到循环中。
    【解决方案2】:

    这是我用来让async with 上下文永远存在的方法:

    def never():
        try:
            return never.never
        except AttributeError:
            never.never = asyncio.Future()
            return never.never
    
    
    async def create_context_and_dependent_tasks():
        async with create_ctx() as ctx:
            for i in range(10):
                asyncio.create_task(use_ctx_indefinitely(bar))
    
            await never()   # **THIS** is the main idea I wanted to talk about.
    

    它不适用于多个循环,但这种情况很少见(可以通过稍微修改 never 函数来解决):

    def never():
        loop = asyncio.get_running_loop()
        try:
            return never.nevers[loop]
        except KeyError:
            never.nevers[loop] = asyncio.Future()
        return never.nevers[loop]
    
    
    never.nevers = {}
    

    问题是,如果你创造了一个本地未来:

    async def foo():
        never = asyncio.Future()
        await never
    

    它会被垃圾收集,任务会被破坏……只是因为 Python 充满了惊喜。所以你必须在全局范围内创建未来(但不是最初,因为循环还不存在)。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-01-11
      • 1970-01-01
      • 1970-01-01
      • 2019-04-14
      • 1970-01-01
      相关资源
      最近更新 更多