12

I am trying to implement a simple idea of passing a data from stdin to a coroutine:

import asyncio
import sys

event = asyncio.Event()

def handle_stdin():
    data = sys.stdin.readline()
    event.data = data  # NOTE: data assigned to the event object
    event.set()

@asyncio.coroutine
def tick():
    while 1:
        print('Tick')
        yield from asyncio.sleep(1)

        if event.is_set():
            data = event.data  # NOTE: data read from the event object
            print('Data received: {}'.format(data))
            event.clear()

def main(): 
    loop = asyncio.get_event_loop()
    loop.add_reader(sys.stdin, handle_stdin)
    loop.run_until_complete(tick())    

if __name__ == '__main__':
    main()

This code works fine, however a simplified version of it with a variable instead of an Event object works too:

data = None

def handle_stdin():
    global data
    data = sys.stdin.readline()

@asyncio.coroutine
def tick():
    while 1:
        print('Tick')
        yield from asyncio.sleep(1)

        global data
        if data is not None:
            print('Data received: {}'.format(data))
            data = None

My questions are: is the approach with Event correct? Or is there a better way with another asyncio objects to handle this kind of problem? Then, if the approach with Event is fine, is using a variable is fine as well?

Thank you.

0

2 Answers 2

17

I think asyncio.Queue is much better suited for this kind of producer/consumer relationship:

import asyncio
import sys

queue = asyncio.Queue()

def handle_stdin():
    data = sys.stdin.readline()
    # Queue.put is a coroutine, so you can't call it directly.
    asyncio.create_task(queue.put(data)) 
    # Alternatively, Queue.put_nowait() is not a coroutine, so it can be called directly.
    # queue.put_nowait(data)

async def tick():
    while 1:
        data = await queue.get()
        print('Data received: {}'.format(data))

def main(): 
    loop = asyncio.new_event_loop()
    loop.add_reader(sys.stdin, handle_stdin)
    loop.run_until_complete(tick())    

if __name__ == '__main__':
    main()

There's less logic involved than with an Event, which you need to make sure you set/unset properly, and there's no need for a sleep, wakeup, check, go back to sleep, loop, like with the global variable. So the the Queue approach is simpler, smaller, and blocks the event loop less than your other possible solutions. The other solutions are technically correct, in that they will function properly (as long as you don't introduce any yield from calls inside if if event.is_set() and if data is not None: blocks). They're just a bit clunky.

Sign up to request clarification or add additional context in comments.

3 Comments

Thank you very much @dano, queue approach looks indeed better than the "event" one.
Is that asyncio.async correct? Gives me a syntax error
@Osman-pasha It was at the time I wrote the naswer, but it has since been deprecated and removed. I updated my answer to use the method that should be called instead.
2

If you want to wait for an event, you should probably be using Event.wait instead of polling is_set.

@asyncio.coroutine
def tick():
    while True:
        yield from event.wait()
        print('Data received: {}'.format(event.data))
        event.clear()

1 Comment

True, in fact even a simple yield from event.wait() without a loop should be sufficient.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.