我想实现并运行自定义Python协同程序(不使用asyncio),以便更好地理解异步机制。
当第一个任务正在等待,什么都不做时,我被期望能够使用并发来启动第二个任务。
这里是堆栈器的同步实现(这是一个任意用例)。
def log(*msg):
print(int(time() - start), ':', *msg)
def stack(stack, item):
sleep(1)
stack.append(item)
start = time()
words = []
stack(words, 'hello')
log(words)
stack(words, 'world')
log(words)
这是输出,正如我所料:
1 : ['hello']
2 : ['hello', 'world']
然后尝试异步实现相同的堆栈器。
def coroutine(func):
def starter(*args, **kwargs):
gen = func(*args, **kwargs)
next(gen)
return gen
return starter
@coroutine
def a_sleep(count):
while True:
yield
sleep(count)
@coroutine
def a_stack(stack):
while True:
item = yield
yield from a_sleep(1)
stack.append(item)
start = time()
words = []
a_stack(words).send('hello')
log(words)
a_stack(words).send('world')
log(words)
# Wait all tasks to finish
sleep(4)
log(words)
预期产出:
0 : []
1 : ['hello', 'world']
5 : ['hello', 'world']
实际输出:
1 : []
2 : []
6 : []
我认为我错过了重要的事情。我希望我的方法是相关的。
使用其他日志,我注意到a_stack函数从不执行追加部分。
答案 0 :(得分:1)
您的问题是您的生成器暂停在yield
函数中的a_sleep()
表达式(通过yield from a_sleep(1)
)委派。那里的生成器还有无限,所以永远不会返回。您永远无法将您的发电机推进到足以达到stack.append(item)
来电。
我认为你误解了yield from
在这里的作用。 yield from
将发电机的控制权移到另一台发电机上;其他生成器必须在yield from
表达式完成并返回之前完成迭代:
>>> @coroutine
... def a_sleep(count):
... while True:
... yield 'sleeping' # to illustrate where we are stuck
... sleep(count)
...
>>> words = []
>>> g = a_stack(words)
>>> g.send('hello')
'sleeping'
>>> g.send('hello')
'sleeping'
>>> g.send('hello')
'sleeping'
而不是使用sleep()
和无限循环,记录时间,并循环直到时间过去:
>>> @coroutine
... def a_sleep(count):
... start = time()
... while int(time() - start) < count:
... yield 'sleeping'
...
>>> g = a_stack(words)
>>> g.send('hello')
'sleeping'
>>> g.send('hello')
'sleeping'
>>> g.send('hello')
>>> words
['hello']
你必须继续迭代你的生成器(在循环中,也许?)让它们交替执行。
asyncio.sleep()
function当然比这更有效率;它使用附加到事件循环提供的Future()
object的AbstactEventLoop.call_later()
functionality。循环让未来的对象知道时间到了什么时候,此时未来被标记为“准备好”&#39;产生它的协程再次继续。
答案 1 :(得分:1)
该行:
a_stack(words).send('hello')
做两件事:
hello
发送到生成器创建的生成器等待项目到达,然后一旦恢复,对项目执行某些操作。这就是问题,你永远不会恢复生成器,你扔掉它并创建一个新的生成器,并继续以相同的方式使用。要修复它,您的发送代码应该执行以下操作:
coro = a_stack(words)
coro.send('hello')
log(words)
coro.send('world')
log(words)
但还有另一个问题。在实际附加到堆栈之前,a_stack
将其执行推迟到另一个迭代器,永远不会停止产生。解决问题的代码a_sleep
的一种方法是:
@coroutine
def a_sleep(count):
t0 = time()
while time() - t0 < count:
yield 'notyet'
然后,您需要一个调度程序或至少一个更具弹性的send
版本,它实际上可以处理延迟执行的任务。一个简单(非常低效)的可能是这样的:
def sync_send(c, v):
while True:
ret = c.send(v)
if ret != 'notyet':
return ret
用coro.send('hello')
替换sync_send(coro, 'hello')
后,会显示预期的输出。
真正的调度程序永远不会繁忙循环;它将由sleep
和其他可能的阻塞调用指示,例如从文件或套接字读取,它必须等待哪些IO /定时事件。在适当的事件到达后,它将唤醒正确的任务。这是asyncio
所做的核心。
要了解有关生成器和yield from
如何用作异步编程的核心抽象的更多信息,我推荐Dave Beazley的精彩讲座Python Concurrency From the Ground。在讲座中,Dave在现场观众面前实施了一个协程调度程序,展示了他的curio库的设计。