Is there a way in Python asyncio to execute first bit of a for loop until a response is received?

huangapple go评论62阅读模式
英文:

Is there a way in Python asyncio to execute first bit of a for loop until a response is received?

问题

Here's the translated portion of your text:

相对于asyncio来说我是新手,我需要知道是否我在做一些根本性的错误。我有一个我想在Python中运行的一般模式,如下所示:

async def function(index):
    print(f'going to sleep: {index}')
    await asyncio.sleep(1) // 一些需要一些时间的函数
    print(f'waking up: {index}')

async def main():
    await asyncio.wait([function(i) for i in range(10)])

我想调用function 10次,而在等待来自asyncio.sleep(1)的响应时,我想继续进行下一次循环迭代。但是,如果调用asyncio.sleep在尝试启动循环的另一次迭代时完成,我希望能处理该响应。

目前,如果我运行这个,我会得到以下输出:

going to sleep: 4
going to sleep: 8
going to sleep: 0
going to sleep: 5
going to sleep: 1
going to sleep: 2
going to sleep: 6
going to sleep: 9
going to sleep: 7
going to sleep: 3
waking up: 4
waking up: 8
waking up: 0
waking up: 5
waking up: 1
waking up: 2
waking up: 6
waking up: 9
waking up: 7
waking up: 3

我希望结果类似于以下内容:

going to sleep: 4
going to sleep: 8
going to sleep: 0
going to sleep: 5
going to sleep: 1
going to sleep: 2
going to sleep: 6
waking up: 4
waking up: 8
waking up: 0
going to sleep: 9
going to sleep: 7
going to sleep: 3
waking up: 5
waking up: 1
waking up: 2
waking up: 6
waking up: 9
waking up: 7
waking up: 3

这个是否在asyncio中可能实现,还是我完全偏离了方向?

谢谢!

英文:

Relatively new to asyncio and I need to know if I'm doing something fundamentally wrong. I have a general pattern I want to run in Python that looks like the following:

async def function(index):
    print(f'going to sleep: {index}')
    await asyncio.sleep(1) // some function that takes some time
    print(f'waking up: {index}')

async def main():
    await asyncio.wait([function(i) for i in range(10)])

I would like to call function 10 times, and while awaiting the response from asyncio.sleep(1) I would like to continue onto the next iteration of my loop. However, if a call to asyncio.sleep finishes while attempting to start another iteration of the loop I would like that response to be dealt with.

Currently, if I run this I get the following output:

going to sleep: 4
going to sleep: 8
going to sleep: 0
going to sleep: 5
going to sleep: 1
going to sleep: 2
going to sleep: 6
going to sleep: 9
going to sleep: 7
going to sleep: 3
waking up: 4
waking up: 8
waking up: 0
waking up: 5
waking up: 1
waking up: 2
waking up: 6
waking up: 9
waking up: 7
waking up: 3

I would like the result to be something similar to the following:

going to sleep: 4
going to sleep: 8
going to sleep: 0
going to sleep: 5
going to sleep: 1
going to sleep: 2
going to sleep: 6
waking up: 4
waking up: 8
waking up: 0
going to sleep: 9
going to sleep: 7
going to sleep: 3
waking up: 5
waking up: 1
waking up: 2
waking up: 6
waking up: 9
waking up: 7
waking up: 3

Is this possible with asyncio or am I completely off the mark?

Thanks

答案1

得分: 0

asyncio.wait函数可以在实际情况下完成你想要的任务。默认情况下,它会等待所有任务完成后再返回,但可以在第一个任务完成后将控制权返回给调用者,这样你可以采取行动,根据其响应创建新任务等等。然后,你可以再次使用wait来等待剩余的(和任何新创建的)任务。

然而,这并不意味着你会看到你在问题中输入的输出 - 每个任务都会在任何其他任务有机会执行其他操作之前“进入”并打印“going to sleep ...”,一旦第一个任务通过其await语句让出事件循环(通过其await语句),下一个准备好的任务就会执行(直到它也等待,然后调用下一个任务,以此类推...)。只有当所有任务都有机会运行其第一部分,直到第一个等待的地方时,才会恢复第一个任务。

随机化睡眠时间将使这更容易可视化:

import random
import asyncio

async def function(index):
    interval = 0.1 + 0.1 * random.randint(0,10)
    print(f'going to sleep: {index} for {interval:.02f}s')
    await asyncio.sleep(interval) # some function that takes some time
    print(f'waking up: {index}')
    return index

async def main():
    tasks = {asyncio.create_task(function(i), name=i) for i in range(10)}
    # All tasks created but none yet started execution, until
    # we run an `await` statement!
    print("starting")
    results = []
    i = 10
    while tasks:
        # Pending tasks are returned in a second set of the "wait" call:
        done, tasks = await asyncio.wait(tasks, return_when=asyncio.FIRST_COMPLETED)
        for task in done:
            results.append(task.result())
            print(f"task {task.get_name()} done!")
        # one can create new tasks here, and just add then to the
        # "tasks" set:
        if len(results) < 5:
            tasks.add(asyncio.create_task(function(i)))
            i += 1

    print(f"results: {sorted(results)}")

asyncio.run(main())

输出:

$ python  test.py 
starting
going to sleep: 0 for 1.10s
going to sleep: 1 for 0.40s
going to sleep: 2 for 0.80s
going to sleep: 3 for 0.10s
going to sleep: 4 for 0.30s
going to sleep: 5 for 1.00s
going to sleep: 6 for 0.60s
going to sleep: 7 for 0.20s
going to sleep: 8 for 0.80s
going to sleep: 9 for 0.80s
waking up: 3
task 3 done!
going to sleep: 10 for 0.60s
waking up: 7
task 7 done!
going to sleep: 11 for 0.70s
waking up: 4
task 4 done!
going to sleep: 12 for 0.70s
waking up: 1
task 1 done!
going to sleep: 13 for 0.60s
waking up: 6
task 6 done!
waking up: 10
task Task-12 done!
waking up: 2
waking up: 8
waking up: 9
task 2 done!
task 9 done!
task 8 done!
waking up: 11
task Task-13 done!
waking up: 5
task 5 done!
waking up: 13
waking up: 12
task Task-14 done!
task Task-15 done!
waking up: 0
task 0 done!
results: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13]
英文:

The asyncio.wait function can do in practical matters(*) what you want. By default, it will wait for all tasks to be completed before returning, but it can be changed to return the control to the caller after the first task is finished - so that you can take action, create new tasks based on its response, and so on - and then you can wait again with the remaining (and any newly created) tasks.

(*) However, this does not mean you will see the output you typed in the question - every task will be entered and print "going to sleep ..." before any other task have a chance to do anything else - as soon as the first task yields to the event loop (through its await statement), the next ready task is executed (up to the point it also awaits, and then the next task is called, etc...). The first task will only be resumed when all the tasks had a chance to run their first part, up to the first await.

Randomizing the sleeping times will make that easier to visualize:

import random
import asyncio


async def function(index):
    interval = 0.1 + 0.1 * random.randint(0,10)
    print(f&#39;going to sleep: {index} for {interval:.02f}s&#39;)
    await asyncio.sleep(interval) # some function that takes some time
    print(f&#39;waking up: {index}&#39;)
    return index

async def main():
    tasks = {asyncio.create_task(function(i), name=i) for i in range(10)}
    # All tasks created but none yet started execution, until
    # we run an `await` statement!
    print(&quot;starting&quot;)
    results = []
    i = 10
    while tasks:
        # Pending tasks are returned in a second set of the &quot;wait&quot; call:
        done, tasks = await asyncio.wait(tasks, return_when=asyncio.FIRST_COMPLETED)
        for task in done:
            results.append(task.result())
            print(f&quot;task {task.get_name()} done!&quot;)
        # one can create new tasks here, and just add then to the
        # &quot;tasks&quot; set:
        if len(results) &lt; 5:
            tasks.add(asyncio.create_task(function(i)))
            i += 1

    print(f&quot;results: {sorted(results)}&quot;)

asyncio.run(main())

(Please, when posting questions, do not skip the "boilerplate" lines needed to run the complete example (the import, and the asyncio.run call in this case). They were simple in this case, but in other questions that is often a barrier to tackle the problem.)

Output:

$ python  test.py 
starting
going to sleep: 0 for 1.10s
going to sleep: 1 for 0.40s
going to sleep: 2 for 0.80s
going to sleep: 3 for 0.10s
going to sleep: 4 for 0.30s
going to sleep: 5 for 1.00s
going to sleep: 6 for 0.60s
going to sleep: 7 for 0.20s
going to sleep: 8 for 0.80s
going to sleep: 9 for 0.80s
waking up: 3
task 3 done!
going to sleep: 10 for 0.60s
waking up: 7
task 7 done!
going to sleep: 11 for 0.70s
waking up: 4
task 4 done!
going to sleep: 12 for 0.70s
waking up: 1
task 1 done!
going to sleep: 13 for 0.60s
waking up: 6
task 6 done!
waking up: 10
task Task-12 done!
waking up: 2
waking up: 8
waking up: 9
task 2 done!
task 9 done!
task 8 done!
waking up: 11
task Task-13 done!
waking up: 5
task 5 done!
waking up: 13
waking up: 12
task Task-14 done!
task Task-15 done!
waking up: 0
task 0 done!
results: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13]

huangapple
  • 本文由 发表于 2023年7月6日 21:11:33
  • 转载请务必保留本文链接:https://go.coder-hub.com/76629216.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定