Skip to content

Instantly share code, notes, and snippets.

@nhoad
Last active March 2, 2023 09:30
Show Gist options
  • Star 28 You must be signed in to star a gist
  • Fork 6 You must be signed in to fork a gist
  • Save nhoad/8966377 to your computer and use it in GitHub Desktop.
Save nhoad/8966377 to your computer and use it in GitHub Desktop.
Async stdio with asyncio
import os
import asyncio
import sys
from asyncio.streams import StreamWriter, FlowControlMixin
reader, writer = None, None
@asyncio.coroutine
def stdio(loop=None):
if loop is None:
loop = asyncio.get_event_loop()
reader = asyncio.StreamReader()
reader_protocol = asyncio.StreamReaderProtocol(reader)
writer_transport, writer_protocol = yield from loop.connect_write_pipe(FlowControlMixin, os.fdopen(1, 'wb'))
writer = StreamWriter(writer_transport, writer_protocol, None, loop)
yield from loop.connect_read_pipe(lambda: reader_protocol, sys.stdin)
return reader, writer
@asyncio.coroutine
def async_input(message):
if isinstance(message, str):
message = message.encode('utf8')
global reader, writer
if (reader, writer) == (None, None):
reader, writer = yield from stdio()
writer.write(message)
yield from writer.drain()
line = yield from reader.readline()
return line.decode('utf8').replace('\r', '').replace('\n', '')
@asyncio.coroutine
def main():
name = yield from async_input("What's your name? ")
print("Hello, {}!".format(name))
asyncio.get_event_loop().run_until_complete(main())
@nhoad
Copy link
Author

nhoad commented Jul 16, 2017

FWIW, there are a couple of issues with this, e.g. python/asyncio#147, and as noted earlier, the fact that print() suddenly becomes dangerously non-blocking. I'd recommend implementing a coroutine that reads from stdin asynchronously, and then makes it synchronous again once done with it. Beware that this will still make stdout (and thus print()) temporarily asynchronous for interactive programs, because of how TTYs are implemented on Linux (and possibly in general). See python/asyncio#213 for some discussion on this.

My earlier statement about "you shouldn't really use sys.stdout use os.fdopen(1, 'wb') blah blah blah" was naive and incorrect, they're exactly equivalent, because that's how file descriptors work, which is why print() becomes dangerous with this snippet.

First idea off the top of my head if you want something more robust, sadly I'd recommend you use a thread for stdio interaction, using a mixture of threading.Event, asyncio.Queue() and loop.call_soon_threadsafe() to activate a read in the thread, and then get the data back to the event loop.

FWIW, if you're writing an application that doesn't interact with the user, or use print(), then you could use the code in the gist just fine.

I hadn't really intended for people to use this gist, so I'm not going to update it or make it any more robust, I just want people to be aware of the issues it has. (this is part where I reveal the snippet has an incredibly restrictive license and wield my lawyer, j/k)

In the spirit of making sure there's no room for ambiguity, I'm releasing this code under The Unlicense. Enjoy the free and subtly broken code folks!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment