I would like to implement and run custom Python coroutines (without using asyncio) to have a better "under the hood" understanding of asynchronous mechanisms.
I was expected to be able to use concurrency to start a second task when the first task is waiting, doing nothing.
Here the synchronous implementation of a stacker (which is an arbitrary use case).
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)
Here the output, as I was expected:
1 : ['hello']
2 : ['hello', 'world']
Then an attempt of the asynchronous implementation of the same stacker.
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)
Expected output:
0 : []
1 : ['hello', 'world']
5 : ['hello', 'world']
Real output:
1 : []
2 : []
6 : []
I figure I missed something important. I hope my approach is relevant.
With additional logs, I have noticed that the a_stack function never execute the append part.
sleep(1)does not yield to other coroutines. It stops the thread entirely.send()on two generators, in sequence.a_sleep. There's nothing wrong with other infinite generators; they're used all over the place in asyncio. (For example,while True: cmd = await reader.readline(); response = execute(cmd); writer.write(response)is an oft-seen pattern with stream-based communication.)