Last active
March 2, 2023 09:30
-
-
Save nhoad/8966377 to your computer and use it in GitHub Desktop.
Async stdio with asyncio
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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()) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
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 thusprint()
) 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()
andloop.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!