简体   繁体   English

在 Windows 上对 os.pipe 进行非阻塞读取

[英]Non blocking read on os.pipe on Windows

This question - How to read from an os.pipe() without getting blocked?这个问题 - 如何从 os.pipe() 读取而不会被阻塞? - shows a solution how to check if os.pipe has any data for Linux, and for this you need to put the pipe into non-blocking mode: - 展示了如何检查os.pipe是否有任何适用于 Linux 的数据的解决方案,为此您需要将管道置于非阻塞模式:

import os, fcntl
fcntl.fcntl(thePipe, fcntl.F_SETFL, os.O_NONBLOCK)

On Windows we have this:在 Windows 上,我们有这个:

ImportError: No module named fcntl

But os.pipe is there:但是os.pipe在那里:

>>> os.pipe()
(3, 4)

So, is it possible to do non-blocking read or peek the contents of os.pipe on Windows?那么,是否可以在 Windows 上进行非阻塞读取或查看os.pipe的内容?

Answering my own question after digging for some time through StackOverflow.通过 StackOverflow 挖掘一段时间后回答我自己的问题。

UPDATE : Things changes thanks to @HarryJohnston.更新:由于@HarryJohnston,事情发生了变化。

At first the answer was no , it is not possible to do non-blocking read on os.pipe on Windows.起初的答案是否定的,不可能在 Windows 上对os.pipe进行非阻塞读取。 From this answer I've got that:这个答案我得到了:

The term for non-blocking / asynchronous I/O in Windows is 'overlapped' - that's what you should be looking at. Windows 中非阻塞/异步 I/O 的术语是“重叠”——这就是你应该关注的。

os.pipe on Windows is implemented through CreatePipe API (see here and ... well, I couldn't find os.pipe code in Python sources ). Windows 上的os.pipe是通过CreatePipe API 实现的(参见这里和...好吧,我在Python 源代码中找不到os.pipe代码)。 CreatePipe makes anonymous pipes, and anonymous pipes do not support asynchronous I/O . CreatePipe制作匿名管道, 匿名管道不支持异步 I/O

But then @HarryJohnston commented that SetNamedPipeHandleState doc allows to put anonymous pipe to non-blocking mode.但随后@HarryJohnston评论说SetNamedPipeHandleState文档允许将匿名管道置于非阻塞模式。 I wrote the test and it failed with OSError: [Errno 22] Invalid argument .我写了这个测试,它失败了OSError: [Errno 22] Invalid argument The error message seemed wrong, so I tried to check what should be return result on non-blocking read operation when data is not available, and after reading MSDN note on named pipe modes I found that it should be ERROR_NO_DATA that has a int value 232. Adding ctypes.WinError() call to exception handler revealed the expected [Error 232] The pipe is being closed.错误消息似乎是错误的,所以我试图检查当数据不可用时非阻塞读取操作的返回结果应该是什么,并且在阅读了关于命名管道模式的 MSDN 注释后,我发现它应该是具有 int 值 232 的ERROR_NO_DATA . 向异常处理程序添加ctypes.WinError()调用显示预期的[Error 232] The pipe is being closed.

So, the answer is yes , it is possible to do non-blocking read on os.pipe on Windows, and here is the proof:所以,答案是肯定的,可以在 Windows 上对os.pipe进行非阻塞读取,这是证明:

import msvcrt
import os

from ctypes import windll, byref, wintypes, GetLastError, WinError
from ctypes.wintypes import HANDLE, DWORD, POINTER, BOOL

LPDWORD = POINTER(DWORD)

PIPE_NOWAIT = wintypes.DWORD(0x00000001)

ERROR_NO_DATA = 232

def pipe_no_wait(pipefd):
  """ pipefd is a integer as returned by os.pipe """

  SetNamedPipeHandleState = windll.kernel32.SetNamedPipeHandleState
  SetNamedPipeHandleState.argtypes = [HANDLE, LPDWORD, LPDWORD, LPDWORD]
  SetNamedPipeHandleState.restype = BOOL

  h = msvcrt.get_osfhandle(pipefd)

  res = windll.kernel32.SetNamedPipeHandleState(h, byref(PIPE_NOWAIT), None, None)
  if res == 0:
      print(WinError())
      return False
  return True


if __name__  == '__main__':
  # CreatePipe
  r, w = os.pipe()

  pipe_no_wait(r)

  print os.write(w, 'xxx')
  print os.read(r, 1024)
  try:
    print os.write(w, 'yyy')
    print os.read(r, 1024)
    print os.read(r, 1024)
  except OSError as e:
    print dir(e), e.errno, GetLastError()
    print(WinError())
    if GetLastError() != ERROR_NO_DATA:
        raise

This answer is basically @anatolytechtonik's answer but with classes.这个答案基本上是@anatolytechtonik 的答案,但有课程。

import msvcrt
import os

# No idea what is going on here but if it works, it works.
from ctypes import windll, byref, wintypes, GetLastError, WinError, POINTER
from ctypes.wintypes import HANDLE, DWORD, BOOL

# ???
LPDWORD = POINTER(DWORD)
PIPE_NOWAIT = wintypes.DWORD(0x00000001)
ERROR_NO_DATA = 232


class AdvancedFD:
    """
    A wrapper for a file descriptor so that we can call:
        `<AdvancedFD>.read(number_of_bytes)` and
        `<AdvancedFD>.write(data_as_bytes)`

    It also makes the `read_fd` non blocking. When reading from a non-blocking
    pipe with no data it returns b"".

    Methods:
        write(data: bytes) -> None
        read(number_of_bytes: int) -> bytes
        rawfd() -> int
        close() -> None
    """
    def __init__(self, fd: int):
        self.fd = fd
        self.closed = False

    def __del__(self) -> None:
        """
        When this object is garbage collected close the fd
        """
        self.close()

    def close(self) -> None:
        """
        Closes the file descriptor.
        Note: it cannot be reopened and might raise an error if it is
        being used. You don't have to call this function. It is automatically
        called when this object is being garbage collected.
        """
        self.closed = True

    def write(self, data: bytes) -> None:
        """
        Writes a string of bytes to the file descriptor.
        Note: Must be bytes.
        """
        os.write(self.fd, data)

    def read(self, x: int) -> bytes:
        """
        Reads `x` bytes from the file descriptor.
        Note: `x` must be an int
              Returns the bytes. Use `<bytes>.decode()` to convert it to a str
        """
        try:
            return os.read(self.fd, x)
        except OSError as error:
            err_code = GetLastError()
            # If the error code is `ERROR_NO_DATA`
            if err_code == ERROR_NO_DATA:
                # Return an empty string of bytes
                return b""
            else:
                # Otherwise raise the error
                website = "https://docs.microsoft.com/en-us/windows/win32/" +\
                          "debug/system-error-codes--0-499-"
                raise OSError("An exception occured. Error code: %i Look up" +\
                              " the error code here: %s" % (err_code, website))

    def config_non_blocking(self) -> bool:
        """
        Makes the file descriptor non blocking.
        Returns `True` if sucessfull, otherwise returns `False`
        """

        # Please note that this is kindly plagiarised from:
        # https://stackoverflow.com/a/34504971/11106801
        SetNamedPipeHandleState = windll.kernel32.SetNamedPipeHandleState
        SetNamedPipeHandleState.argtypes = [HANDLE, LPDWORD, LPDWORD, LPDWORD]
        SetNamedPipeHandleState.restype = BOOL

        handle = msvcrt.get_osfhandle(self.fd)

        res = windll.kernel32.SetNamedPipeHandleState(handle,
                                                      byref(PIPE_NOWAIT), None,
                                                      None)
        return not (res == 0)

    def rawfd(self) -> int:
        """
        Returns the raw fd as an int.
        """
        return self.fd


class NonBlockingPipe:
    """
    Creates 2 file descriptors and wrapps them in the `AdvancedFD` class
    so that we can call:
        `<AdvancedFD>.read(number_of_bytes)` and
        `<AdvancedFD>.write(data_as_bytes)`

    It also makes the `read_fd` non blocking. When reading from a non-blocking
    pipe with no data it returns b"".

    Methods:
        write(data: bytes) -> None
        read(number_of_bytes: int) -> bytes
        rawfds() -> (int, int)
        close() -> None
    """
    def __init__(self):
        self.read_fd, self.write_fd = self.create_pipes()
        self.read_fd.config_non_blocking()

    def __del__(self) -> None:
        """
        When this object is garbage collected close the fds
        """
        self.close()

    def close(self) -> None:
        """
        Note: it cannot be reopened and might raise an error if it is
        being used. You don't have to call this function. It is automatically
        called when this object is being garbage collected.
        """
        self.read_fd.close()
        self.write_fd.close()

    def create_pipes(self) -> (AdvancedFD, AdvancedFD):
        """
        Creates 2 file descriptors and wrapps them in the `Pipe` class so
        that we can call:
            `<Pipe>.read(number_of_bytes)` and
            `<Pipe>.write(data_as_bytes)`
        """
        read_fd, write_fd = os.pipe()
        return AdvancedFD(read_fd), AdvancedFD(write_fd)

    def write(self, data: bytes) -> None:
        """
        Writes a string of bytes to the file descriptor.
        Note: Must be bytes.
        """
        self.write_fd.write(data)

    def read(self, number_of_bytes: int) -> bytes:
        """
        Reads `x` bytes from the file descriptor.
        Note: `x` must be an int
              Returns the bytes. Use `<bytes>.decode()` to convert it to a str
        """
        return self.read_fd.read(number_of_bytes)

    def rawfds(self) -> (int, int):
        """
        Returns the raw file descriptors as ints in the form:
            (read_fd, write_fd)
        """
        return self.read_fd.rawfd(), self.write_fd.rawfd()


if __name__  == "__main__":
    # Create the nonblocking pipe
    pipe = NonBlockingPipe()

    pipe.write(b"xxx")
    print(pipe.read(1024)) # Check if it can still read properly

    pipe.write(b"yyy")
    print(pipe.read(1024)) # Read all of the data in the pipe
    print(pipe.read(1024)) # Check if it is non blocking

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM