1

I want to use the asyncio library on Windows to read file-like objects (such as sys.stdin and serial ports).

However, asyncio on Windows expects readable objects to be sockets.

Is it possible to write an adapter class to wrap a file-like object with the API of a socket so that I could use stdin and serial ports with asyncio?

If so, please could you give an example because I've never used sockets before?

Community
  • 1
  • 1
blokeley
  • 6,726
  • 9
  • 53
  • 75

1 Answers1

2

Short answer

No and maybe yes.

Long answer

You can't just wrap a file-like object as a socket, or vice-versa, and expect it to work. asyncio is using system calls under the hood to do its async magic and stdin is stdin from the system's point of view no mater how much you wrap it. When using the default SelectorEventLoop it uses one of the select-like system calls. On Windows it uses the select system call, which does not support anything but sockets.

So, select sucks on windows. Is there another option? Yes. On Windows, and only Windows, there is another API for doing async operations called IOCP (I/O Completion Ports). It's a "notify-on-completion" type multiplexer as opposed to the select based "notify-when-ready" type multiplexers. The API is much more complicated than doing a simple select call on a file, but fortunately asyncio has some support for it already.

The ProactorEventLoop uses IOCP on Windows and it should theoretically supports reading from stdin. I don't have access to a Windows machine so I can't test this, but give this a go:

# vim: filetype=python3 tabstop=2 expandtab

import asyncio as aio
import os
import sys

if os.name == "nt":
  proactor_loop = aio.ProactorEventLoop()
  aio.set_event_loop(proactor_loop)

@aio.coroutine
def main(loop):
  stdin_reader = aio.StreamReader()
  stdin_transport, stdin_protocol = yield from loop.connect_read_pipe(
    lambda: aio.StreamReaderProtocol(stdin_reader),
    sys.stdin
  )

  line = yield from stdin_reader.read()
  print(line.strip().decode("utf-8"))

  stdin_transport.close()

loop = aio.get_event_loop()
loop.run_until_complete(main(loop))
loop.close()

And run the equivalent of this on Windows:

$ echo blha blha blha | python test.py
blha blha blha

If this works, then at least you can do async stdin on Windows. And then you could try something similar for stdout/stderr or perhaps even serial ports.

If all else fails, you could always simulate async behaviour by wrapping blocking calls in threads using the loop.run_in_executor coroutine:

yield from loop.run_in_executor(None, sys.stdin.readline)
Jashandeep Sohi
  • 4,903
  • 2
  • 23
  • 25
  • Example doesn't work unfortunatelly :( Fails at "yield from stdin_reader.read()" part with "OSError: [WinError 6] The handle is invalid" error – mangolier Dec 09 '15 at 15:20
  • Ahh, I was afraid it wouldn't work. --- Using a thread pool (i.e. `run_in_executor`) should work though and should be sufficient for most use cases. – Jashandeep Sohi Dec 09 '15 at 20:02
  • Rather than use `sys.stdin`, would `os.fdopen(sys.stdin.fileno(), 'rb')` work? Pass in a new binary file object for the file handle, not the textiowrapper. – Martijn Pieters Sep 11 '18 at 10:16