IO Completion Ports (IOCP) and Asynchronous I/O through STDIN, STDOUT and STDERR
tl;dr Can't be done directly. You have two options: a) mock async I/O with threads, or b) redirect STDIN, STDOUT & STDERR to other file handles that support overlapping (aka non-blocking/async) I/O,...
IO Completion Ports (IOCP) and Asynchronous I/O through STDIN, STDOUT and STDERR favicon https://tim.mcnamara.nz/post/176613307022/iocp-and-stdio

こういうやつ

+-------+
| child |
|process|
+-------+
stdin ^ | stdout
| v
+-------+
|process|
+-------+
-> stdin stdout ->

subprocess モジュール

subprocess は、

os.system
os.spawn*

の置き換え。

subprocess.run

中でPopenして結果を集めて CompletedProcess として返す。

# 抜粋
def run(...):
with Popen(*popenargs, **kwargs) as process:
try:
stdout, stderr = process.communicate(input, timeout=timeout)
return CompletedProcess(process.args, retcode, stdout, stderr)

実行して結果の文字列を得て終わりというタイプの用途向け。 旧 os.system の代替になると思う。

subprocess.Popen

標準入力、標準出力を制御するのはこっち。

subprocess.Popen(['cmd_name', 'arg0', 'arg1'...],
stdin=subprocess.PIPE,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
cwd=None, env=None,
text=False
)

Readループが一個しかない時はこれでいいんでないかな。

今回のテーマ

+-------+
| child |
|process|
+-------+
stdin ^ | @stdout
| v
+-------+
|process| ここでロギングして通信内容を確認したい
+-------+
@stdin ^ | stdout
| v
+-------+
| parent|
|process|
+-------+
  • @のところを常時読み込みにしたい(2つのReadループ)
  • @stdin をReadするとブロックして固まるのでつらい

つらいのだ。

asyncio

2つのReaderを非同期で制御しようということで 。

asyncio の基本

loop を露出させる。

loop は暗黙。 基本的にこちらでよいと思う。 必要に応じて取得する。

asyncio.create_task で新しいスタックを開始する

新しいスタックなのでエラーハンドリングが無いことに注意。

StreamReaderProtocol と StreamWriterProtocol

コールバックと Stream を結び付ける。

Windowsの標準入出力はIOCPできない

IOCPできるハンドルは決まっていて、

python3.7 で asyncio.create_subprocess ができた

child process 側はこれで助かった。

なんか、たまに socket.exception が出るので、 tcpのlocalhost接続にリダイレクトするとか謎の技使っているのかもしれぬ。

おかげで、子プロセスの標準入出力から StreamReaderStreamWriter を楽に取得できる。

重い NativeCoroutine は、ThreadPoolExecutorに逃がす

標準入力側

GILを回避して、別スレッドで待てるのではないか。

どんな処理が、NativeCoroutine なのか。