我想从另一个任务中停止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()
答案 0 :(得分:1)
我想从另一个任务中停止python asyncio任务,并在第二个任务中的某些情况发生时再次启动它。
我假设您控制任务的创建,但不想涉及协程的实现。在您的情况下,您控制coroutine2
和main_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()