【问题标题】:Python asyncio: stop the loop when one coroutine is donePython asyncio:完成一个协程后停止循环
【发布时间】:2017-01-05 09:45:46
【问题描述】:

我是这个 python asyncio 主题的新手。我有一个简单的问题: 我有一个包含两个要同时运行的协程的任务。第一个 coroutine(my_coroutine) 只会连续打印一些东西,直到达到 second_to_sleep。第二个协程(seq_coroutine)将一个接一个地依次调用其他 4 个协程。我的目标是在 seq_coroutine 完全完成时停止循环。确切地说,我希望 my_coroutine 在 seq_coroutine 完成之前一直存在。有人可以帮我吗?

我的代码是这样的:

import asyncio

async def my_coroutine(task,  seconds_to_sleep = 3):
    print("{task_name} started\n".format(task_name=task))
    for i in range(1, seconds_to_sleep):
        await asyncio.sleep(1)
        print("\n{task_name}: second {seconds}\n".format(task_name=task, seconds=i))

async def coroutine1():
    print("coroutine 1 started")
    await asyncio.sleep(1)
    print("coroutine 1 finished\n")


async def coroutine2():
    print("coroutine 2 started")
    await asyncio.sleep(1)
    print("coroutine 2 finished\n")


async def coroutine3():
    print("coroutine 3 started")
    await asyncio.sleep(1)
    print("coroutine 3 finished\n")


async def coroutine4():
    print("coroutine 4 started")
    await asyncio.sleep(1)
    print("coroutine 4 finished\n")


async def seq_coroutine():
    await coroutine1()
    await coroutine2()
    await coroutine3()
    await coroutine4()

def main():
    main_loop = asyncio.get_event_loop()
    task = [asyncio.ensure_future(my_coroutine("task1", 11)),
            asyncio.ensure_future(seq_coroutine())]
    try:
        print('loop is started\n')
        main_loop.run_until_complete(asyncio.gather(*task))
    finally:
        print('loop is closed')
        main_loop.close()


if __name__ == "__main__":
    main()

这是这个程序的输出:

loop is started

task1 started

coroutine 1 started

task1: second 1

coroutine 1 finished
coroutine 2 started

task1: second 2

coroutine 2 finished
coroutine 3 started

task1: second 3

coroutine 3 finished
coroutine 4 started

task1: second 4

coroutine 4 finished

task1: second 5
task1: second 6
task1: second 7
task1: second 8
task1: second 9
task1: second 10

loop is closed

我只想拥有这样的东西:

loop is started

task1 started

coroutine 1 started

task1: second 1

coroutine 1 finished
coroutine 2 started

task1: second 2

coroutine 2 finished
coroutine 3 started

task1: second 3

coroutine 3 finished
coroutine 4 started

task1: second 4

coroutine 4 finished

loop is closed

【问题讨论】:

  • 为什么不只是run_until_complete(seq_coroutine)
  • 同时执行此操作的想法不仅仅是睡眠和打印。 “my_coroutine”是一个监听其他东西的过程,应该和 seq_coroutine 并行运行,但是为了更容易询问,我只是把它缩短了。

标签: python coroutine python-asyncio


【解决方案1】:

我刚刚为我的问题找到了合适的解决方案。 我不会删除我的帖子,我会发布我的解决方案,以便它可以帮助面临同样问题的其他人。 我使用了asyncio.wait(task, return_when=asyncio.FIRST_COMPLETED),只要第一个任务完成,它就会返回结果。 这是解决方案:

import asyncio
from asyncio.tasks import FIRST_COMPLETED
from concurrent.futures import CancelledError

async def my_coroutine(task,  seconds_to_sleep = 3):
    print("{task_name} started\n".format(task_name=task))
    for i in range(1, seconds_to_sleep):
        await asyncio.sleep(1)
        print("\n{task_name}: second {seconds}\n".format(task_name=task, seconds=i))

async def coroutine1():
    print("coroutine 1 started")
    await asyncio.sleep(1)
    print("coroutine 1 finished\n")


async def coroutine2():
    print("coroutine 2 started")
    await asyncio.sleep(1)
    print("coroutine 2 finished\n")


async def coroutine3():
    print("coroutine 3 started")
    await asyncio.sleep(1)
    print("coroutine 3 finished\n")


async def coroutine4():
    print("coroutine 4 started")
    await asyncio.sleep(1)
    print("coroutine 4 finished\n")


async def seq_coroutine(loop):
    await coroutine1()
    await coroutine2()
    await coroutine3()
    await coroutine4()

def main():
    main_loop = asyncio.get_event_loop()
    task = [asyncio.ensure_future(my_coroutine("task1", 11)),
            asyncio.ensure_future(seq_coroutine(main_loop))]
    try:
        print('loop is started\n')
        done, pending = main_loop.run_until_complete(asyncio.wait(task, return_when=asyncio.FIRST_COMPLETED))
        print("Completed tasks: {completed}\nPending tasks: {pending}".format(completed = done, pending = pending))

        #canceling the tasks
        for task in pending:
            print("Cancelling {task}: {task_cancel}".format(task=task, task_cancel=task.cancel()))

    except CancelledError as e:
        print("Error happened while canceling the task: {e}".format(e=e))
    finally:
        print('loop is closed')


if __name__ == "__main__":
    main()

【讨论】:

    【解决方案2】:

    您可以使用变量向另一个协程发出信号。常用asyncio.Event

    import asyncio
    
    import random
    
    
    async def clock(name, event):
        print("* {} started".format(name))
        i = 0
        while not event.is_set():
            await asyncio.sleep(0.1)
            i += 1
            print("* {}: {}".format(name, i))
        print("* {} done".format(name))
        return i
    
    
    async def coro(x):
        print("coro() started", x)
        await asyncio.sleep(random.uniform(0.2, 0.5))
        print("coro() finished", x)
    
    
    async def seq_coroutine(name):
        event = asyncio.Event()
        clock_task = asyncio.ensure_future(clock(name, event))
        # await asyncio.sleep(0) # if you want to give a chance to clock() to start
        await coro(1)
        await coro(2)
        await coro(3)
        await coro(4)
        event.set()
        i = await clock_task
        print("Got:", i)
    
    
    def main():
        main_loop = asyncio.get_event_loop()
        main_loop.run_until_complete(seq_coroutine("foo"))
        main_loop.close()
    
    
    if __name__ == "__main__":
        main()
    

    您也可以使用await event.wait() 阻止一段代码,直到设置事件:

    async def xxx(event):
        print("xxx started")
        await event.wait()
        print("xxx ended")
    

    【讨论】:

      【解决方案3】:

      这是做同样事情的另一种方法,我认为在表示工作之间的依赖关系方面更清晰:

      import asyncio
      
      async def poll():
          i = 0
          while True:
              print("First", i)
              i += 1
              await asyncio.sleep(20)
              print("Second", i)
              i += 1
              await asyncio.sleep(20)
              
      async def stop():
          poller = asyncio.ensure_future(poll())
          await asyncio.sleep(5)
          poller.cancel()
      
      main_loop = asyncio.get_event_loop()
      main_loop.run_until_complete(stop())
      main_loop.close()
      
      

      基本上,我们不是在单个作业结束时中断整个事件循环然后取消那里的作业,而是在父作业完成时直接取消依赖作业。

      【讨论】:

        猜你喜欢
        • 2016-03-12
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2022-10-31
        • 1970-01-01
        • 1970-01-01
        • 2020-02-26
        • 2022-01-11
        相关资源
        最近更新 更多