Python asyncio:在不丢失状态的情况下从另一个任务停止并启动一个任务?

时间:2018-11-29 14:00:30

标签: python python-asyncio

我想从另一个任务中停止python asyncio 任务,并在第二个任务中的某些情况发生时再次启动它。

请注意,我不想取消第一个任务的协程(协程停止时的状态应该可用)。另外,我不在乎第一个任务的确切状态,我只希望事件循环停止运行第一个任务,直到第二个任务告诉其他情况为止。

我希望该示例代码有助于理解问题:

import asyncio
async def coroutine1():
    i = 0
    while(True):
        i += 1
        print("coroutine1: " + str(i) )
        await asyncio.sleep(1)

async def coroutine2(task1):
    i = 0
    while(True):
        i += 1
        if (i > 3) and (i<10):
            pass #TODO: stop task1 here
        else:
            pass #TODO: Maybe check if task1 is running
                 #and start task1 again if it's not?
        print("coroutine2: " + str(i) )
        await asyncio.sleep(1)

async def main_coroutine():
    loop = asyncio.get_event_loop()
    task1 = loop.create_task(coroutine1())
    task2 = loop.create_task(coroutine2(task1))
    done, pending = await asyncio.wait(
                [task1, task2]
                , return_when=asyncio.FIRST_COMPLETED,)


loop = asyncio.get_event_loop()
loop.run_until_complete(main_coroutine())
loop.close()

2 个答案:

答案 0 :(得分:1)

  

我想从另一个任务中停止python asyncio任务,并在第二个任务中的某些情况发生时再次启动它。

我假设您控制任务的创建,但不想涉及协程的实现。在您的情况下,您控制coroutine2main_coroutine,但不能控制coroutine1的内部。

在这种情况下,您可以将协程包裹在一个__await__中,而不是正常的yield from循环,而是检查stopped标志并等待将来告诉它何时进行操作恢复。

class Stoppable:
    def __init__(self, coro):
        self._coro_iter = coro.__await__()
        self._stopped = None

    def __await__(self):
        while True:
            while self._stopped:
                print('awaiting stopped')
                yield from self._stopped.__await__()
            try:
                v = next(self._coro_iter)
            except StopIteration as e:
                return v
            yield v

    def stop(self):
        loop = asyncio.get_event_loop()
        self._stopped = loop.create_future()

    def start(self):
        if self._stopped is not None:
            self._stopped.set_result(None)
            self._stopped = None

您可以使用包装程序修改coroutine2,以随意停止和恢复执行coroutine1

async def coroutine2(s):
    i = 0
    while True:
        i += 1
        if i == 3:
            print('stopping coroutine1')
            s.stop()
        elif i == 10:
            print('restarting coroutine1')
            s.start()
        print("coroutine2: " + str(i) )
        await asyncio.sleep(1)

async def main_coroutine():
    loop = asyncio.get_event_loop()
    s = Stoppable(coroutine1())
    fut1 = asyncio.ensure_future(s)
    task2 = loop.create_task(coroutine2(s))
    done, pending = await asyncio.wait(
        [fut1, task2], return_when=asyncio.FIRST_COMPLETED)

包装器的工作方式是展开yield from中固有的循环。例如,要将__await__委托给另一个协程,可以这样写:

    def __await__(self):
        yield from self._coro_iter

这样写,您无法实现停止,因为yield from包含一个隐式循环,该循环产生由底层迭代器产生的所有值-类似于:

    def __await__(self):
        while True:
            try:
                v = next(self._coro_iter)
            except StopIteration as e:
                return e.value
            yield v

这样,添加一个if就很容易了,它可以在每次迭代遍历时检查_stopped,这意味着每次我们由事件循环恢复时。剩下的障碍是,在_stopped被废除之前,不能只是忙于循环-我们必须产生其他东西,以允许事件循环恢复运行其他协程。幸运的是,通过使_stopped成为未来并从未来中屈服,很容易实现。确定未来的结果后,我们将自动恢复并继续执行包装好的协程。

答案 1 :(得分:0)

似乎无法完成。

可以使用task1.cancel()取消正在进行的任务,也可以使用asyncio.get_event_loop().create_task(newTask)创建新任务。

也可以使用task1._coro获取正在运行的任务的协程,但是如果我们尝试使用先前安排的协程再次创建任务,我们将获取 RuntimeError异常。他们决定讨论的地方是: https://bugs.python.org/issue25887

最后,一种实现欲望效果的可能方法是使用asyncio.Queue对象:

import asyncio 
async def coroutine1(stop_queue):
    i = 0
    while(True):
        if stop_queue.empty(): #if the queue is empty keep working.
            i += 1
            print("coroutine1: " + str(i) )
        await asyncio.sleep(1)

async def coroutine2(stop_queue):
    i = 0
    while(True):
        i += 1
        if i == 3:
                await stop_queue.put("whatever..") #put something in the queue
        if i == 11:
                await stop_queue.get() #take something from the queue
        print("coroutine2: " + str(i) )
        await asyncio.sleep(1)

async def main_coroutine():
    stop_queue    = asyncio.Queue()
    done, pending = await asyncio.wait(
                [coroutine1(stop_queue), coroutine2(stop_queue)]
                , return_when=asyncio.ALL_COMPLETED,)

loop = asyncio.get_event_loop()
loop.run_until_complete(main_coroutine())
loop.close()