简体   繁体   English

如何为stdin / stdout创建asyncio流读取器/写入器?

[英]How to create asyncio stream reader/writer for stdin/stdout?

I need to write two programs which will be run as a parent process and its child. 我需要编写两个程序,它们将作为父进程及其子进程运行。 The parent process spawns the child and then they communicate via pair of pipes connected to child's stdin and stdout. 父进程生成子进程,然后通过连接到子进程stdin和stdout的一对管道进行通信。 The communication is peer-to-peer, that's why I need asyncio. 通信是点对点的,这就是我需要asyncio的原因。 A simple read/reply loop won't do. 一个简单的读/回放循环是行不通的。

I have written the parent. 我写过父母。 No problem because asyncio provides everything I needed in create_subprocess_exec() . 没问题,因为asyncio提供了我在create_subprocess_exec()所需的一切。

However I don't know how to create a similar stream reader/writer in the child. 但是我不知道如何在孩子中创建类似的流读取器/写入器。 I did not expect any problems. 我没想到会有任何问题。 because the pipes are already created and file descriptors 0 and 1 are ready to use when the child process starts. 因为已经创建了管道,并且在子进程启动时可以使用文件描述符0和1。 No connection is to be open, no process needs to be spawned. 没有连接是打开的,不需要生成任何进程。

This is my not working attempt: 这是我不努力的尝试:

import asyncio
import sys

_DEFAULT_LIMIT = 64 * 1024

async def connect_stdin_stdout(limit=_DEFAULT_LIMIT, loop=None):
    if loop is None:
        loop = asyncio.get_event_loop()
    reader = asyncio.StreamReader(limit=limit, loop=loop)
    protocol = asyncio.StreamReaderProtocol(reader, loop=loop)
    r_transport, _ = await loop.connect_read_pipe(lambda: protocol, sys.stdin)
    w_transport, _ = await loop.connect_write_pipe(lambda: protocol, sys.stdout)
    writer = asyncio.StreamWriter(w_transport, protocol, reader, loop)
    return reader, writer

The problem is I have two transports where I should have one. 问题是我有两个运输工具,我应该有一个。 The function fails, because it tries to set the protocol's transport twice: 该函数失败,因为它尝试将协议的传输设置两次:

await loop.connect_read_pipe(lambda: protocol, sys.stdin)
await loop.connect_write_pipe(lambda: protocol, sys.stdout)
# !!!! assert self._transport is None, 'Transport already set'

I tried to pass a dummy protocol to the first line, but this line is not correct either, because both transports are needed, not just one: 我试图将伪协议传递给第一行,但这行也不正确,因为需要两个传输,而不仅仅是一个:

writer = asyncio.StreamWriter(w_transport, protocol, reader, loop)

I guess I need to combine two unidirectional transports to one bidirectional somehow. 我想我需要以某种方式将两个单向传输组合到一个双向。 Or is my approach entirely wrong? 或者我的方法完全错了? Could you please give me some advice? 你能给我一些建议吗?


UPDATE : after some test this seems to work (but does not look good to me): 更新 :经过一些测试后,这似乎有效(但对我来说不好看):

async def connect_stdin_stdout(limit=_DEFAULT_LIMIT, loop=None):
    if loop is None:
        loop = asyncio.get_event_loop()
    reader = asyncio.StreamReader(limit=limit, loop=loop)
    protocol = asyncio.StreamReaderProtocol(reader, loop=loop)
    dummy = asyncio.Protocol()
    await loop.connect_read_pipe(lambda: protocol, sys.stdin) # sets read_transport
    w_transport, _ = await loop.connect_write_pipe(lambda: dummy, sys.stdout)
    writer = asyncio.StreamWriter(w_transport, protocol, reader, loop)
return reader, writer

Your first version fails because you are using the wrong protocol for the writer side; 您的第一个版本失败,因为您使用了错误的协议作者; the StreamReaderProtocol implements hooks to react to incoming connections and data, something the writing side doesn't and shouldn't have to deal with. StreamReaderProtocol实现了对传入连接和数据作出反应的钩子,这是写作方不会也不应该处理的事情。

The loop.connect_write_pipe() coroutine uses the protocol factory you pass in and returns the resulting protocol instance. loop.connect_write_pipe()协程使用您传入的协议工厂并返回生成的协议实例。 You do want to use that same protocol object in the stream writer, instead of the protocol used for the reader. 您确实希望在流编写器中使用相同的协议对象,而不是用于阅读器的协议。

Next, you do not want to pass the stdin reader to the stdout stream writer! 接下来,您希望将stdin读取器传递给stdoutstdin器! That class assumes that the reader and writer are connected to the same file descriptor, and that's really not the case here. 该类假定读者和编写者连接到同一个文件描述符,而这种情况实际上并非如此。

In the recent past I've build the following to handle stdio for a child process; 最近的过去,我构建了以下内容来处理子进程的stdio; the stdio() function is based on the Nathan Hoad gist on the subject , plus a fallback for Windows where support for treating stdio as pipes is limited . stdio()函数基于Nathan Hoad关于该主题的要点 ,加上Windows的后备,其中支持将stdio视为管道的限制

You do want the writer to handle backpressure properly, so my version uses the (undocumented) asyncio.streams.FlowControlMixin class as the protocol for this; 你确实希望asyncio.streams.FlowControlMixin正确处理背压,所以我的版本使用(未记录的) asyncio.streams.FlowControlMixin类作为协议; you really don't need anything more than that: 你真的不需要更多:

import asyncio
import os
import sys

async def stdio(limit=asyncio.streams._DEFAULT_LIMIT, loop=None):
    if loop is None:
        loop = asyncio.get_event_loop()

    if sys.platform == 'win32':
        return _win32_stdio(loop)

    reader = asyncio.StreamReader(limit=limit, loop=loop)
    await loop.connect_read_pipe(
        lambda: asyncio.StreamReaderProtocol(reader, loop=loop), sys.stdin)

    writer_transport, writer_protocol = await loop.connect_write_pipe(
        lambda: asyncio.streams.FlowControlMixin(loop=loop),
        os.fdopen(sys.stdout.fileno(), 'wb'))
    writer = asyncio.streams.StreamWriter(
        writer_transport, writer_protocol, None, loop)

    return reader, writer

def _win32_stdio(loop):
    # no support for asyncio stdio yet on Windows, see https://bugs.python.org/issue26832
    # use an executor to read from stdio and write to stdout
    # note: if nothing ever drains the writer explicitly, no flushing ever takes place!
    class Win32StdinReader:
        def __init__(self):
            self.stdin = sys.stdin.buffer 
        async def readline():
            # a single call to sys.stdin.readline() is thread-safe
            return await loop.run_in_executor(None, self.stdin.readline)

    class Win32StdoutWriter:
        def __init__(self):
            self.buffer = []
            self.stdout = sys.stdout.buffer
        def write(self, data):
            self.buffer.append(data)
        async def drain(self):
            data, self.buffer = self.buffer, []
            # a single call to sys.stdout.writelines() is thread-safe
            return await loop.run_in_executor(None, sys.stdout.writelines, data)

    return Win32StdinReader(), Win32StdoutWriter()

While perhaps outdated a little, I found this 2016 blog post by by Nathaniel J. Smith on asyncio and curio to be hugely helpful in understanding how asyncio, protocols, transports and backpressure and such all interact and hang together. 虽然可能已经过时了一点,但我发现Nathaniel J. Smith在2016年的博客文章中对asyncio和curio 非常有帮助,因为它非常有助于理解asyncio,协议,传输和背压等所有这些内容的相互作用和挂起。 That article also shows why creating the reader and writer objects for stdio is so verbose and cumbersome at the moment. 该文章还说明了为什么为stdio创建reader和writer对象目前是如此冗长和繁琐。

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

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