等待asyncio coroutines按特定顺序完成

时间:2017-01-14 12:13:28

标签: python concurrency python-asyncio coroutine

我有一个带有一堆asyncio.coroutine的守护程序,可以归结为类似的东西

import asyncio
import signal

class Daemon:

    def __init__(self, loop=asyncio.get_event_loop()):
        self.loop = loop
        self.running = False
        self.tasks = {
            'coroutine1': asyncio.ensure_future(self.coroutine1()),
            'coroutine2': asyncio.ensure_future(self.coroutine2()),
        }

    def run(self):
        self.running = True
        for task in self.tasks.values():
            task.add_done_callback(self.task_done_callback)
        # gracefuly close everything when SIGINT (could be ^C) is received
        self.loop.add_signal_handler(signal.SIGINT, self.close)
        self.loop.run_forever()

    def close(self):
        self.running = False
        self.loop.run_until_complete(self.tasks['coroutine1'])
        self.loop.run_until_complete(self.tasks['coroutine2'])

    def task_done_callback(self, future):
        for task in self.tasks.values():
            if not task.done():
                return
        self.loop.stop()

    @asyncio.coroutine
    def coroutine1(self):
        while self.running:
            print('coroutine1: do stuff')
            yield from asyncio.sleep(1)

    @asyncio.coroutine
    def coroutine2(self):
        while self.running:
            print('coroutine2: do some other stuff')
            yield from asyncio.sleep(3)

if __name__ == '__main__':
    loop = asyncio.get_event_loop()
    daemon = Daemon(loop)
    daemon.run()
    loop.close()

当程序收到SIGINT时,守护程序会正常关闭。发生这种情况时,会触发close()方法,该方法负责通知所有正在运行的任务,他们应该完成工作并停止。只需将running设置为False即可完成此操作。每当任务完成时,都会触发Daemon.task_done_callback。它会检查是否所有任务都已完成,如果是这样,那么它就会停止循环。

这里的问题是close()方法不起作用。这是因为我在循环播放时通过loop.run_until_complete调用run_forever。这产生RuntimeError('This event loop is already running')

重要的是: coroutine1需要在coroutine2之前完成,因为coroutine1如果coroutine2不再执行其操作,可能会遇到问题。< /强>

我的问题是如何确保coroutine1之前完成coroutine2

1 个答案:

答案 0 :(得分:0)

这是实现这一目标的一种方式。 (我删除了signal部分,只限制了coroutine1的运行时间。

import asyncio

class Daemon:

    def __init__(self, loop=asyncio.get_event_loop()):
        self.loop = loop
        self.tasks = {
            'coroutine1': asyncio.ensure_future(self.coroutine1()),
            'coroutine2': asyncio.ensure_future(self.coroutine2())}

    def run(self):
        for task in self.tasks.values():
            task.add_done_callback(self.task_done_callback)
        self.loop.run_forever()

    def task_done_callback(self, future):
        if all(task.done() for task in self.tasks.values()):
            self.loop.stop()

    @asyncio.coroutine
    def coroutine1(self):
        for _ in range(5):
            print('coroutine1: doing stuff')
            yield from asyncio.sleep(0.2)
        print('coroutine1: done!')

    @asyncio.coroutine
    def coroutine2(self):
        while not self.tasks['coroutine1'].done():
            print('coroutine2: doing stuff while coro1 is running')
            yield from asyncio.sleep(0.2)
        print('coroutine2: doing stuff after coro1 has ended')
        yield from asyncio.sleep(1)
        print('coroutine2: done!')

if __name__ == '__main__':
    loop = asyncio.get_event_loop()
    daemon = Daemon(loop)
    daemon.run()

这里最重要的是检查coroutine1是否仍在运行(可以仅查询您的self.tasks属性。)

现在要将其与signal集成以停止coroutine1我建议您注册一个设置标记的简单函数(例如self.signal_flag)。然后使用类似coroutine1的内容在while not self.signal_flag: ...内循环。这些是我建议的完整解决方案的变化:

class Daemon:

    def __init__(self, loop=asyncio.get_event_loop()):
        ...
        self.signal_flag = False

    def run(self):
        ...
        self.loop.add_signal_handler(signal.SIGINT, self.set_signal_flag)
        self.loop.run_forever()

    def set_signal_flag(self):
        print('caught signal!')
        self.signal_flag = True

    async def coroutine1(self):
        while not self.signal_flag:
            print('coroutine1: doing stuff')
            await asyncio.sleep(0.2)
        print('coroutine1: done!')

还要注意,不需要close循环。在解释器继续运行的IDE中,这将使事情变得比它们需要的更复杂......

从python 3.5开始,你可以(并且可能应该)将这种语法用于你的协同程序:

async def coroutine1(self):
    for _ in range(5):
        print('coroutine1: doing stuff')
        await asyncio.sleep(0.2)
    print('coroutine1: done!')