【问题标题】:Run tasks immediately and then wait立即运行任务然后等待
【发布时间】:2020-06-11 20:48:09
【问题描述】:

我是 Python 新手,代码类似于以下内容:

import time
import asyncio

async def my_async_function(i):
    print("My function {}".format(i))

async def start():
    requests = []

    # Create multiple requests
    for i in range(5):
        print("Creating request #{}".format(i))
        requests.append(my_async_function(i))

    # Do some additional work here
    print("Begin sleep")
    time.sleep(10)
    print("End sleep")

    # Wait for all requests to finish
    return await asyncio.gather(*requests)

asyncio.run(start())

无论“额外工作”需要多长时间,请求似乎只在“结束睡眠”之后运行。我猜asyncio.gather 是真正开始执行它们的原因。我怎样才能让请求(又名my_async_function())立即开始,做额外的工作,然后等待所有的最后完成?

编辑: 根据 Krumelur 的 cmets 和我自己的发现,我正在寻找以下结果:

import time
import asyncio
import random

async def my_async_function(i):
    print("Begin function {}".format(i))
    await asyncio.sleep(int(random.random() * 10))
    print("End function {}".format(i))

async def start():
    requests = []

    # Create multiple requests
    for i in range(10):
        print("Creating request #{}".format(i))
        requests.append(asyncio.create_task(my_async_function(i)))

    # Do some additional work here
    print("Begin sleep")
    await asyncio.sleep(5)
    print("End sleep")

    # Wait for all requests to finish
    return await asyncio.gather(*requests)

asyncio.run(start())

这仅在my_async_function 和“附加工作”都处于等待状态时才有效,以便事件循环可以给它们每个执行时间。你需要create_task(如果你知道它是协程)或ensure_future(如果它可能是协程或未来)来允许请求立即运行,否则它们仍然只有在你gather时才会运行。

【问题讨论】:

    标签: python python-asyncio


    【解决方案1】:

    time.sleep()是同步操作

    你会想要使用异步睡眠并等待它, 例如

    await asyncio.sleep(10)
    

    其他异步代码只会在当前任务产生时运行(即通常在“等待”某事时)。

    使用异步代码意味着您必须在任何地方继续使用异步。异步操作适用于 I/O-bound 应用程序。如果“额外工作”主要受 CPU 限制,则最好使用线程(但要注意全局解释器锁!)

    【讨论】:

    • 哦,所以如果我有任何同步代码,它会阻塞异步代码并阻止它运行?我在想它可以以某种方式在后台产生一个线程并继续我的同步代码。换句话说,Python 是带有事件循环或类似的单线程的吗?
    • 因此,在本练习中,我使用time.sleep() 模拟了同步工作。如果我正在进行不可等待的处理(例如一些数学计算)怎么办?我不能同时运行其他代码吗?
    • 是的。事件循环是单线程的,在大多数语言中都是如此。有时您可以等待线程连接等,但在 Python 中也有全局解释器锁,这意味着一次只有一个线程将运行 Python 代码(为 I/O 产生),所以它与使用 async/await 没有什么不同(仅允许 C 模块释放 GIL)
    • 如果您的数学运算快速转义为本地代码(例如繁重的 NymPy 运算),您可能会使用线程。如果循环驻留在 Python 中,则不能,您应该查看生成并行 Python 运行时并通过队列等共享原语同步的多处理模块。
    • 我认为您应该探索使用线程/进程池的替代方案。你说你受CPU限制。 HTTP 请求几乎不需要任何 CPU,因此您可能不会受 CPU 限制而导致网络请求饿死,但如果您是,python 中唯一的解决方案是进程池。 'futures' 是一个很好的接口,见github.com/ross/requests-futures
    最近更新 更多