A non-blocking read on a subprocess.PIPE in Python
Неблокирующее чтение в подпроцессе.КАНАЛ в Python
Я использую модуль подпроцесса для запуска подпроцесса и подключения к его выходному потоку (стандартный вывод). Я хочу иметь возможность выполнять неблокирующие чтения в стандартном выводе. Есть ли способ сделать .readline неблокирующим или проверить, есть ли данные в потоке, прежде чем я вызову.readline? Я бы хотел, чтобы это было переносимым или, по крайней мере, работало под Windows и Linux.
Вот как я это делаю на данный момент (это блокируется в .readline, если данные недоступны):
p = subprocess.Popen('myprogram.exe', stdout = subprocess.PIPE) output_str = p.stdout.readline()
Надежный способ чтения потока без блокировки независимо от операционной системы - использовать Queue.get_nowait():
import sys from subprocess import PIPE, Popen from threading import Thread
try: from queue import Queue, Empty except ImportError: from Queue import Queue, Empty # python 2.x
ON_POSIX = 'posix'in sys.builtin_module_names
defenqueue_output(out, queue): for line initer(out.readline, b''): queue.put(line) out.close()
p = Popen(['myprogram.exe'], stdout=PIPE, bufsize=1, close_fds=ON_POSIX) q = Queue() t = Thread(target=enqueue_output, args=(p.stdout, q)) t.daemon = True# thread dies with the program t.start()
# ... do other things here
# read line without blocking try: line = q.get_nowait() # or q.get(timeout=.1) except Empty: print('no output yet') else: # got line # ... do something with line
Ответ 2
У меня часто возникала похожая проблема; программам на Python, которые я часто пишу, необходимо иметь возможность выполнять некоторые основные функциональные возможности, одновременно принимая вводимые пользователем данные из командной строки (stdin). Простое перенесение функций обработки пользовательского ввода в другой поток не решает проблему, потому что readline() блокируется и не имеет таймаута. Если основная функциональность завершена и больше нет необходимости ждать дальнейшего ввода данных пользователем, я обычно хочу, чтобы моя программа завершила работу, но это невозможно, потому что readline() все еще блокируется в другом потоке, ожидающем строки. Решение, которое я нашел для этой проблемы, состоит в том, чтобы сделать stdin неблокирующим файлом с помощью модуля fcntl:
import fcntl import os import sys
# make stdin a non-blocking file fd = sys.stdin.fileno() fl = fcntl.fcntl(fd, fcntl.F_GETFL) fcntl.fcntl(fd, fcntl.F_SETFL, fl | os.O_NONBLOCK)
# user input handling thread while mainThreadIsRunning: try: input = sys.stdin.readline() except: continue handleInput(input)
На мой взгляд, это немного чище, чем использование модулей select или signal для решения этой проблемы, но опять же, это работает только в UNIX...
Ответ 3
В Unix-подобных системах и Python 3.5+ есть os.set_blocking который делает именно то, что написано.
import os import time import subprocess
cmd = 'python3', '-c', 'import time; [(print(i), time.sleep(1)) for i in range(5)]' p = subprocess.Popen(cmd, stdout=subprocess.PIPE) os.set_blocking(p.stdout.fileno(), False) start = time.time() whileTrue: # first iteration always produces empty byte string in non-blocking mode for i inrange(2): line = p.stdout.readline() print(i, line) time.sleep(0.5) if time.time() > start + 5: break p.terminate()
#!/usr/bin/env python3.5 import asyncio import locale import sys from asyncio.subprocess import PIPE from contextlib import closing
asyncdefreadline_and_kill(*args): # start child process process = await asyncio.create_subprocess_exec(*args, stdout=PIPE)
# read line (sequence of bytes ending with b'\n') asynchronously asyncfor line in process.stdout: print("got line:", line.decode(locale.getpreferredencoding(False))) break process.kill() returnawait process.wait() # wait for the child process to exit